Files
rippr/tool/parity/probe.dart
Dylan 398157b1f5 Complete Phase 1: accumulator, exports, and full parity coverage
T05 — ride_accumulator.dart with the cross-batch anchor intact (12 tests).
T06 — ride_export.dart, GPX 1.1 and GeoJSON (15 tests), parsed with real
parsers rather than substring matching.
T07 — parity harness extended to cover export byte output.

GPX and GeoJSON are byte-identical across Kotlin and Dart: same length, same
FNV hash, including the escaped hostile name and all %.7f/%.1f formatting.

Two harness bugs found and fixed while building it. String.hashCode is not
comparable across Java and Dart, so text comparison used FNV-1a instead. And a
missing jar let a failed Kotlin compile pass as a green run -- run.sh now checks
for the artifact and exits non-zero, the same failure mode as the v2 sweep that
hid a compile error behind /dev/null.

Phase 1 done: 79 tests passing, analyze clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 01:08:30 -05:00

103 lines
3.7 KiB
Dart

// ignore_for_file: avoid_print -- this is a command-line harness; stdout is the output.
// Dart side of the cross-language parity harness (T07).
// Must stay fixture-for-fixture identical to tool/parity/main.kt.
import 'dart:math' as math;
import 'package:rippr/src/domain/models.dart';
import 'package:rippr/src/export/ride_export.dart';
import 'package:rippr/src/geo/geo.dart';
import 'package:rippr/src/stats/ride_statistics.dart';
/// Same LCG as the Kotlin oracle. Dart ints are 64-bit two's complement on the VM and
/// multiplication wraps, matching Kotlin's Long.
class Lcg {
Lcg(this._s);
int _s;
double nextDouble() {
_s = _s * 6364136223846793005 + 1442695040888963407;
final bits = (_s >>> 11) & ((1 << 53) - 1);
return bits / (1 << 53);
}
}
/// FNV-1a 32-bit. Dart's String.hashCode differs from Java's, so it cannot be used to
/// compare text across languages -- this can.
int fnv1a(String s) {
var h = 2166136261;
for (final c in s.codeUnits) {
h ^= c;
h = (h * 16777619) & 0xFFFFFFFF;
}
return h;
}
TrackPoint p(int seg, int ts, double lat, double lon, double sp, double alt, int id) =>
TrackPoint(id: id, tripId: 1, segmentId: seg, timestamp: ts,
latitude: lat, longitude: lon, speedKmh: sp, altitudeM: alt);
void main() {
void out(String k, Object v) => print('$k=$v');
out('haversine_calgary_edmonton', haversineMeters(51.0447, -114.0719, 53.5461, -113.4938));
out('haversine_short_hop', haversineMeters(51.0447, -114.0719, 51.04480, -114.0719));
out('perp_beyond_end', perpendicularDistanceMeters(
const LatLon(51.003, -114.0), const LatLon(51.000, -114.0), const LatLon(51.002, -114.0)));
final ride = List.generate(21600,
(i) => LatLon(51.0 + i * 0.00001, -114.0 + math.sin(i / 100.0) * 0.001));
final simplified = simplify(ride, 5.0);
out('simplify_count', simplified.length);
out('simplify_last_lat', simplified.last.lat);
out('path_length_ride', pathLengthMeters(ride));
final rng = Lcg(42);
final noisy = List.generate(600,
(i) => p(1, i * 1000, 51.0, -114.0, 0.0, 1000.0 + (rng.nextDouble() * 16.0 - 8.0), i + 1));
out('noisy_gain', computeSummary(noisy).elevationGainM);
out('noisy_loss', computeSummary(noisy).elevationLossM);
final climb = List.generate(101,
(i) => p(1, i * 1000, 51.0 + i * 0.0001, -114.0, 50.0, 1000.0 + i, i + 1));
out('climb_gain', computeSummary(climb).elevationGainM);
final run = List.generate(100,
(i) => p(1, i * 1000, 51.0 + i * 0.0001, -114.0, 40.0, 1000.0, i + 1));
final s = computeSummary(run);
out('run_distance', s.distanceM);
out('run_moving_millis', s.movingMillis);
out('run_avg_speed', s.avgMovingSpeedKmh);
final prof = elevationProfile(run, maxSamples: 200);
out('profile_size', prof.length);
out('profile_last_distance', prof.last.distanceM);
final hist = speedHistogram(run, bucketKmh: 10);
out('hist_buckets', hist.length);
out('hist_first_millis', hist.first.millis);
// --- export byte output -------------------------------------------------
const etrip = Trip(
id: 1,
startedAt: 1700000000000,
endedAt: 1700000600000,
state: TripState.completed,
distanceM: 12345.6,
maxSpeedKmh: 118.4);
const esegs = [
Segment(id: 1, tripId: 1, startedAt: 0, endedAt: 1),
Segment(id: 2, tripId: 1, startedAt: 2, endedAt: 3),
];
final epts = <TrackPoint>[
for (final sg in esegs)
for (var i = 0; i < 5; i++)
p(sg.id, 1700000000000 + i * 1000, 51.0447 + i * 0.0001, -114.0719, 72.0,
1045.0, i + 1),
];
final g = gpx(etrip, esegs, epts, "Sam & Dave's <ride>");
final j = geoJson(etrip, esegs, epts, "Sam & Dave's <ride>");
out('gpx_len', g.length);
out('gpx_fnv', fnv1a(g));
out('geojson_len', j.length);
out('geojson_fnv', fnv1a(j));
}