Skip to content

Extend the bit-exact oracle to swe_house_pos and hsys 'J'/'Z'/'0'; triage the DATA-MISSING residual - #40

Merged
Tim81 merged 9 commits into
mainfrom
oracle-house-pos-and-data-missing-triage
Aug 9, 2026
Merged

Extend the bit-exact oracle to swe_house_pos and hsys 'J'/'Z'/'0'; triage the DATA-MISSING residual#40
Tim81 merged 9 commits into
mainfrom
oracle-house-pos-and-data-missing-triage

Conversation

@Tim81

@Tim81 Tim81 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • swe_house_pos and house systems 'J'/'Z'/'0' are now bit-exact verified against the pinned 2.10.03 C (3,240 new rows, both grids SHA-256 identical between the C and .NET dumps, confirmed again on Linux with gcc against the actual committed grids).
  • Fixes a stale $HouseLetters exclusion comment ('J' was already implemented, the comment wasn't) and documents a real, pre-existing stack-overflow defect in the vendored 2.08 C's swe_house_pos (hsys 'G') that this addition surfaced — not a port bug, not fixed here.
  • Corrects docs/known-issues.md's swe_rise_trans/!do_fixstar entry: the conformance oracle already covers the fixed-star path (10 real, passing rows) even though the baseline can't, by design.
  • Triages the full 759-row DATA-MISSING population against a fresh MSVC build of the pinned C on widened ephemeris data: 514 pass outright, and of the 245 that come back VALUE-MISMATCH-shaped, all 245 classify DRIFT (t.exp is stale, not the port) — 0 confirmed PORT-DEFECT. known-fail.tsv itself is unchanged; findings are recorded in docs/compliance-2.10.03.md and Tests/conformance/regenerations.log.

Note: this branch was cut from local main, which was already 3 commits ahead of origin/main before this work started (4b7a938, b6c09dd, f9aa71e) — those are included here too since they weren't pushed yet.

Test plan

  • scripts/run-oracle-dump.ps1 + scripts/verify-oracle.ps1 — both grids SHA-256 identical, 0 known-diff rows
  • scripts/verify-grid-header-parity.ps1, scripts/verify-sedump-macro-parity.ps1, scripts/verify-doc-counts.ps1 — all green
  • dotnet test Tools/OracleVerify.Tests — 93/93, both Windows and Linux
  • Linux (gcc 13.3.0, Docker): rebuilt sedump.c, replayed the actual committed grid-analytic.tsv/grid-files.tsv — SHA-256 identical to the .NET dump, matching what linux-exactness CI checks
  • dotnet test Tests/SwissEphNet.Conformance.Tests — 67/67 on net8.0 and net10.0, unchanged from before this branch
  • dotnet build SwissEphNet.CrossPlatform.slnf -c Release --no-incremental — clean, no new warnings
  • macos-exactness — not verified locally (no macOS environment available); expected to pass (plain C99, no platform-specific code) but unmeasured

- C.printf.cs: implement the \xhh hex-escape TODO in ReplaceMetaChars, capped
  at one byte (two hex digits). Not a transliteration of Swiss Ephemeris C
  (this method is third-party printf boilerplate copied in verbatim, per its
  own header comment), so there's no C source to match; added test coverage
  for the new escapes and kept the existing \x-with-no-digits case unchanged.
- docs/known-issues.md: the DIR_GLUE section still said its README
  breaking-change note "has not been added there yet," but that note landed
  in an earlier commit. Removed the stale sentence and fixed a drifted
  Sweph.cs line citation (2807 to 2880).
- docs/known-issues.md + scripts/validate-seeded-areas.py: the 30
  CDEF|-1|*/CUDEF|-1|* rows recorded as an "unattributed mechanism" for
  SE_ECL_NUT turned out to be a leak in replay_calc_defaulteph's shared
  pyswisseph session: swi_check_nutation skips recomputing nutation whenever
  NONUT is set (forced on for J2000/SIDEREAL too), so an unreset session
  reads back whatever a prior row left cached. Verified against real C in
  isolation with sedump.exe, which resets state before every row and
  returned the same zero output as the port for all 30 cases. Reset the
  replay harness's state between rows to match, the same way
  replay_datetime already handles this.
@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown

Greptile Summary

The PR extends bit-exact oracle coverage for swe_house_pos and additional house systems, records the DATA-MISSING triage, and includes several portability and compatibility corrections.

  • Adds C and .NET oracle dispatch, grids, field labels, classifications, and verification coverage.
  • Updates compliance and known-issue documentation with the new measurements.
  • Restores prior CPointer<T>.ToArray() semantics for negative and past-end base indices.
  • Adds a narrow macOS exception for documented HOUSEPOS|Y NaN-sign differences.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
SwissEphNet/Tools/CPointer.cs Clamps the source index before copying so negative indices return the full array and past-end indices return an empty array, resolving the prior reported regression.
Tests/SwissEphNet.Tests/CPointerTest.cs Adds regression coverage for negative and past-end pointer-to-array conversions.
Tools/CReference/sedump.c Extends the C oracle driver to cover the newly supported house-position and house-system cases.
Tools/OracleDump/Program.cs Extends the managed oracle dump path in parallel with the C reference coverage.
.github/workflows/oracle.yml Updates expected oracle counts and applies a narrowly scoped macOS comparison exception for documented NaN-sign divergence.
docs/compliance-2.10.03.md Records expanded bit-exact coverage and the results of the DATA-MISSING residual classification.

Reviews (3): Last reviewed commit: "Extend the HOUSEPOS|Y NaN-sign waiver to..." | Re-trigger Greptile

Comment thread SwissEphNet/Tools/CPointer.cs Outdated
Tim81 added 7 commits August 9, 2026 08:19
None of these touch SwissEphNet/CPort or either frozen Program.cs -- no
transliteration-freeze exception needed.

- SwissEph.Format.cs: FormatToDegreeMinuteSecond's format-string scan
  allocated a fresh 2-element char[] (new char[]{'d','D'}, etc.) on every
  loop iteration just to compare two known characters. Replaced with two
  char locals per branch, '\0' as the "no match" sentinel instead of null.
- Tools/CPointer.cs: ToArray() and the explicit T[] conversion operator
  built the result via BaseArray.Skip(BaseIndex).ToArray(), which
  allocates a LINQ iterator on top of the destination array. Replaced
  with a direct Array.Copy. Array.Copy throws ArgumentException on an
  out-of-range sourceIndex even at a copy length of 0, unlike the old
  Skip().ToArray(), which tolerated BaseIndex overrunning BaseArray's end
  by returning an empty array -- guarded with a length clamp and skip the
  copy call when there's nothing to copy, and added
  TestToArrayWithBaseIndexPastEnd to pin it. No current caller reaches
  this edge (CPointer.cs's own Length getter already assumes the same
  invariant elsewhere), but Array.Copy's stricter behavior was a real
  silent change from the LINQ version's.
- Extensions/StringExtensions.cs: Contains(string, char) allocated a
  1-char string every call via s.Contains(c.ToString()) (needed on
  netstandard2.0/net4x, which have no native Contains(char) overload --
  see the existing comment on why s.Contains(c) can't be called directly
  there). Replaced with s.IndexOf(c) >= 0: IndexOf is a distinct method
  name from Contains, so it carries none of the recursion risk that
  ruled out calling Contains(char) directly, and it exists on every
  targeted TFM without allocating. Contains(string, char[]) now delegates
  to the fixed single-char overload instead of repeating the same
  c.ToString() pattern per array element.

A fourth candidate -- dropping an unnecessary (double[]) cast in CPort's
swi_cartpol_sp (SwephLib.cs:377) -- was reverted after review. It matched
external/swisseph/swephlib.c:370's zero-copy pointer semantics and was
behaviorally identical either way, but every existing freeze-exception
precedent (the freeze's own two documented examples at Sweph.cs:2634 and
Sweph.cs:6800, all 26 scripts/freeze-manifest-log.txt entries) corrects
an actual *observable* divergence -- wrong output, a crash, wrong text --
not a structural difference with provably identical output. Accepting
"closer to the C's syntax, same result" as grounds would justify
stripping array materialization anywhere in CPort under the same
reasoning, which is broader than what the freeze's one permitted
exception is meant to cover.

Verified: dotnet build SwissEphNet.CrossPlatform.slnf -c Release
--no-incremental clean on all three TFMs (netstandard2.0/net8.0/net10.0,
0 errors, no new warnings); dotnet test Tests/SwissEphNet.Tests 321/321
on net8.0 and net10.0; dotnet test Tests/NetStandard20Smoke.Tests 12/12
on net462 and net48 (the only suite that forces netstandard2.0
resolution, exercising the StringExtensions and CPointer changes
directly); scripts/verify-freeze.ps1 confirms SwissEphNet/CPort,
Programs/SweTest/Program.cs and Programs/SweMini/Program.cs are
byte-for-byte unchanged.
The port has tracked v2.10.3bfinal (the same tag the C reference is built
from) for a while now, but several checked-in comments still described the
conformance oracle as if the port were lagging the corpus by a version --
true when first written, not true today. Found while explaining what
Tests/conformance/known-fail.tsv's 1,423 rows actually represent and
discovering the "most iterations fail because the port is at 2.08" framing
doesn't hold up: Tools/OracleVerify's PORT-VERSION diff category (the same
concept for the bit-exact oracle and the swetest text diff) has zero rows
in every known-diff file in this repo, confirming the port has caught up
in every gate that tracks this.

docs/known-issues.md gains a new entry, "What
Tests/conformance/known-fail.tsv's 1,423 rows actually are", recording the
real breakdown: 759 DATA-MISSING rows never cross-checked against a real C
build, and of the 664 VALUE-MISMATCH rows that were driven through a fresh
MSVC build of the same pinned C (Tests/conformance/value-mismatch-
triage.tsv), 664 reproduced the port's own output rather than t.exp's --
t.exp is a static Astrodienst snapshot dated 14.12.2023, not a live
reference -- and only 4 were ever confirmed genuine port defects. Those
four (suite 1 testcase 1, iterations 377/379/383/385, SE_INTP_PERG's
missing JD-range guard) were already fixed prior to this commit; verified
directly on the current tree (Sweph.cs:1188-1195 carries the guard, the
four rows are absent from known-fail.tsv, conformance suite passes 67/67
on both TFMs) rather than assumed from the fix's own commit message.

ConformanceReport.cs, ConformanceDispatcher.cs, DiffCategory.cs and
verify-swetest-diff.ps1 each carried their own version of the same stale
framing in doc comments; corrected to match and cross-reference the new
known-issues.md entry instead of repeating the numbers inline, so they
don't go stale the same way again.

Tests/conformance/regenerations.log is deliberately left untouched --
its "port at 2.08 vs corpus 2.10.03" line was accurate when written
(2026-07-28, before the 2.10.03 upgrade work landed) and is an append-only
historical log, the same convention scripts/freeze-manifest-log.txt
follows.

No frozen-file changes (verified via scripts/verify-freeze.ps1); build
clean on all three TFMs; Tests/SwissEphNet.Conformance.Tests 67/67 on
net8.0 and net10.0.
…iage the DATA-MISSING residual

swe_house_pos had no bit-exact coverage at all before this change -- neither
Tools/CReference/sedump.c nor Tools/OracleDump/Program.cs called it, only
t.exp (conformance suite 6.6) and a pyswisseph replay reached it. Adds a
HOUSE_POS row generator to gen-grid-analytic.ps1 (3,240 rows: every hsys
letter this port implements except 'G', see below, plus 'Z'/'0' as
deliberate default/Placidus-fallback sentinels), reusing this grid's
existing xin0/xin1 columns (added earlier for AZALT) for swe_house_pos's
own xpin[0]/xpin[1] -- no new column needed. Matching process_house_pos
(sedump.c) and ProcessHousePos (OracleDump) added; FieldLabels.cs gains the
HOUSEPOS case its own hardcoded func-token switch requires.

$HouseLetters also gains 'J': the exclusion comment claimed the port did
not implement it yet, which was stale -- SwissEphNet/CPort/SweHouse.cs
already has real case 'J' blocks (cusp computation and house name both),
and the pinned C (v2.10.3bfinal) implements it too. 'J' now crosses
HOUSES/HOUSES_ARMC/HOUSES_EX/HOUSES_EX2/HOUSES_ARMC_EX2 alongside every
other letter, not only HOUSE_NAME's own separate list.

'G' (Gauquelin) is excluded from HOUSE_POS specifically: Astrodienst's own
vendored 2.08 C declares hcusp[36] inside swe_house_pos, one slot short of
the 37 the 2.10.03 C declares for the same function -- a real, pre-existing
defect in historical, unmodified upstream C, reproduced directly (a
two-row grid isolating 'G' crashes the 2.08 sedump build with a
stack-buffer-overrun fault). The pinned 2.10.03 build has no such defect,
confirmed directly. Not a port bug and not fixable here.

One driver-code-only fix found along the way: this grid's first-ever NaN
result (hsys 'Y''s already-documented near-singularity, now reached
through swe_house_pos too) showed MSVC's printf rendering NaN differently
from .NET's ToString -- the underlying bit pattern already matched.
sedump.c's emit_value now special-cases isnan to print "NaN" uniformly.

scripts/verify-oracle.ps1 reports both grids SHA-256 identical between the
C and .NET dumps with the addition in place, confirmed again on Linux
(gcc 13.3.0, matching linux-exactness's own build recipe) against the
actual committed grids, not just a scratch subset.

Also corrects docs/known-issues.md's swe_rise_trans/!do_fixstar entry: the
characterization baseline genuinely cannot reach the fixed-star path (by
design, it never subscribes to OnLoadFile), but the conformance oracle
already does -- t.exp carries 10 real ipl=-1/star=Regulus rows across
suite 9 testcases 1 and 2, all currently passing. The prior entry's
"genuinely uncheckable in this repository" language overstated a
baseline-specific limit as a repository-wide one.

Separately, triages the full 759-row DATA-MISSING population against a
fresh MSVC build of the pinned C, on the same widened ephemeris data
(era files, ephe/sat/, JPL DE431, and -- for the last 22 rows -- the two
per-asteroid files se00433s.se1/se00010s.se1 obtained from Astrodienst's
own public distribution): 514 rows pass outright once real data is
present, and of the 245 that come back VALUE-MISMATCH-shaped instead,
all 245 classify DRIFT (t.exp is stale, not the port) -- 0 confirmed
PORT-DEFECT. Findings recorded in docs/compliance-2.10.03.md and
Tests/conformance/regenerations.log; known-fail.tsv itself is unchanged,
and every temporary file/opt-in used for the probe was reverted before
this commit.
The Array.Copy-based ToArray() computed length as
Math.Max(0, BaseArray.Length - BaseIndex), which for a negative BaseIndex
produces a length LARGER than BaseArray.Length, then called Array.Copy with
a negative sourceIndex -- Array.Copy rejects a negative sourceIndex
unconditionally, so this threw ArgumentOutOfRangeException. The prior
LINQ-based BaseArray.Skip(BaseIndex).ToArray() treated a non-positive count
as Skip(0), .NET's own documented behavior, and returned the full array
unchanged.

Clamps the copy's start index to zero (not just the length), matching
Skip's actual semantics for both a negative BaseIndex (full array) and one
past the end (empty array, the case the existing
TestToArrayWithBaseIndexPastEnd regression test already covers). Adds
TestToArrayWithNegativeBaseIndex alongside it.
…S addition

scripts/classify-oracle-versions.ps1 was never re-run after extending the
bit-exact oracle to swe_house_pos and hsys 'J'/'Z'/'0' -- oracle.yml
regenerates both files from a fresh run and fails on any difference from
what is committed, which is exactly what the new HOUSE_POS/'J'/'Z'/'0' case
ids (absent from the previously-committed classification) triggered.

Analytic: 22,289 -> 27,437 cases classified, 5,227 changed (the new rows,
plus every existing case_id whose ordinal position shifted). Files:
3,280 -> 3,376 cases, 111 changed. No TRACKS-2.08 or TRACKS-NEITHER rows in
either grid -- every new HOUSE_POS/'J'/'Z'/'0' case classifies AGREES-BOTH
or TRACKS-2.10.03 (already ported, ahead of 2.08), the same two buckets
this grid's other case ids already split into.
Apple's libSystem and the port produce the two different IEEE-754 NaN bit
patterns (0xfff8... vs 0x7ff8...) for a handful of hsys 'Y' HOUSE_POS rows
near the same near-singularity already documented in known-issues.md's
swe_house_pos cross-platform section. Both sides already render the value
as "NaN" text; only the hex payload differs. Unlike Windows'
known-diff.tsv, this job had no waiver mechanism at all, so any future
platform-specific NaN-payload divergence in this exact class would need
one anyway -- match on the structural signal (HOUSEPOS|Y case_id prefix +
NaN on both sides' decimal column) rather than hardcoding case_ids, and
print what got excepted so a silent change stays visible in the log.
@Tim81
Tim81 force-pushed the oracle-house-pos-and-data-missing-triage branch from 8fe0010 to a54d9cb Compare August 9, 2026 06:21
…ison

The AOT-published binary runs the identical port code against the
identical libm as the JIT comparison, so it hit the same two known
HOUSEPOS|Y NaN-sign-only rows the prior commit already waived in the
JIT step -- missed there because that step's own gate wasn't touched
until this run actually exercised it. Apply the identical structural
filter (HOUSEPOS|Y case_id prefix + NaN on both sides' decimal column)
so the AOT step's real purpose -- catching genuine AOT-vs-JIT codegen
divergence -- isn't buried under a divergence that's neither.
@Tim81
Tim81 merged commit 3329c3f into main Aug 9, 2026
59 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant