mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
Both hosts routed verbs themselves and both policies enumerated their own actions, and all four drifted. Web sent `swipes` to scrollable containers only, so swipe-to-dismiss on a list row was reachable on native and unreachable on web; the model policy folded gestures its own way and could not reach what the seeded picker drew. A host now reports facts about every element and never decides which verb may act on it: targets.ts acceptsTarget owns that for both. pick.ts builtinCandidates is the single enumeration, and the model policy reads it through __sanderlingEnumerateBuiltin__ instead of reimplementing it in Go. Gesture verbs change with it: scrolls stay vertical over scrollable containers, swipes go free-form in all four directions from any element with real bounds. Claude-Session: https://claude.ai/code/session_01Fj4wJUikdABuMQEETwW55J
57 lines
2.6 KiB
TypeScript
57 lines
2.6 KiB
TypeScript
// Shared cross-runtime parity scenario: the FIXED seed, FIXED target list,
|
|
// and FIXED action root that both the node test (parity.test.ts) and the goja
|
|
// test (internal/verifier/parity_test.go) drive. Each side runs the SAME
|
|
// pick.ts over the SAME Pcg and asserts the SAME committed golden
|
|
// (fixtures/parity-golden.json); matching one golden on both sides proves the
|
|
// two engines agree without either invoking the other.
|
|
//
|
|
// The target ORDER and the per-tick PCG draw order are the parity contract.
|
|
// The weighted root mixes a tap branch (1 candidate draw) with a typing branch
|
|
// (1 candidate draw + 1 corpus draw), so a tick exercises weighted selection, a
|
|
// builtin, and the input corpus together; reordering candidates or adding or
|
|
// dropping a draw on either side shifts the stream and fails the golden.
|
|
|
|
import { Pcg } from "../src/pcg.ts";
|
|
import { nextAction } from "../src/pick.ts";
|
|
import { serializeAction, type SerializedAction } from "../src/runtime-entry.ts";
|
|
import { taps, typing, weighted } from "../src/actions.ts";
|
|
import type { GeneratorNode, Host, TargetElement } from "../src/action-tree.ts";
|
|
|
|
export const PARITY_SEED_HI = 0x9e3779b97f4a7c15n;
|
|
export const PARITY_STEPS = 20;
|
|
|
|
// Every fact is set so the shared eligibility rule admits all three targets for
|
|
// every verb, leaving the draw order as the only variable.
|
|
const EVERY_FACT = { clickable: true, enabled: true, editable: true, scrollable: true };
|
|
|
|
export const PARITY_TARGETS: TargetElement[] = [
|
|
{ x: 50, y: 60, selector: "id:alpha", width: 100, height: 40, ...EVERY_FACT },
|
|
{ x: 150, y: 160, selector: "id:beta", width: 120, height: 48, ...EVERY_FACT },
|
|
{ x: 250, y: 260, selector: "id:gamma", width: 80, height: 32, ...EVERY_FACT },
|
|
];
|
|
|
|
// A 3:1 weighted split over taps and typing. The stub host offers the same
|
|
// target list to every verb so the only variables are the draw order and the JS
|
|
// engine's number/bigint behavior.
|
|
export const PARITY_ROOT: GeneratorNode = weighted([3, taps], [1, typing]);
|
|
|
|
const HOST: Host = {
|
|
platform: () => "android",
|
|
queryTargets: () => PARITY_TARGETS,
|
|
reportUnsupported: () => {},
|
|
seedHi: () => PARITY_SEED_HI,
|
|
seedLo: () => 0n,
|
|
};
|
|
|
|
// runParity emits the parity scenario's action stream from a fresh Pcg. It
|
|
// drives pick.ts directly (not installRuntime, whose globals are locked once)
|
|
// so the caller can run it repeatedly to assert determinism.
|
|
export function runParity(): (SerializedAction | null)[] {
|
|
const rng = new Pcg(PARITY_SEED_HI, 0n);
|
|
const stream: (SerializedAction | null)[] = [];
|
|
for (let i = 0; i < PARITY_STEPS; i++) {
|
|
stream.push(serializeAction(nextAction(PARITY_ROOT, rng, HOST)));
|
|
}
|
|
return stream;
|
|
}
|