feat(06-07): PURE projectTurnCards + makeStreetAt + the 21 turn-card copy keys (GREEN)
- cron.ts: linear progress projection (clamped), stateless 180 s cadence (due = nowSec % 180 < 60), dead-trip gate, classifyTurn-authority next-turn detection over consecutive-leg bearings, 4-line HTML caption anatomy - D-12 street by DEFAULT: makeStreetAt hops snapToGraph → matched segment's nameId → nameAt (never the node index); snap miss → bare-verb degradation; matched-unnamed → cardStreetUnnamed - strings.ts: cardAction* (8) + cardActionBare* (8) + cardStreetUnnamed + cardEtaTemplate + cardHonesty + cardOpenButton, en + ms verbatim - GC1-GC8 + CS7 green: 116 passed, 0 failed; bot:typecheck exit 0
This commit is contained in:
@@ -96,22 +96,22 @@ function envelope({ points, distanceM, durationS, startedAtSec, chatId = 101 })
|
||||
|
||||
/* ————— GC1 — the projection math (fixed nowSec, fake street resolver) ————— */
|
||||
|
||||
test("GC1 projection: T+300 of 900 → the 90° left carded at T-01, 667 M, ETA 13:15 · 6.7 KM LEFT", async () => {
|
||||
test("GC1 projection: elapsed 180 s of 900 → the 90° left carded at T-01, 2.0 KM, ETA 13:15 · 8.0 KM LEFT", async () => {
|
||||
const cron = await import("../src/guidance/cron.ts");
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 });
|
||||
const card = cron.projectTurnCards(env, T0 + 300, () => "Jalan Sultan Ismail");
|
||||
assert.equal(card.due, true, "T0 % 180 === 0 → the window's first tick is due");
|
||||
assert.equal(card.glyph, "left", "the first classified turn ahead (progress 3333 < along 4000) is the 90° left");
|
||||
const card = cron.projectTurnCards(env, T0 + 180, () => "Jalan Sultan Ismail");
|
||||
assert.equal(card.due, true, "T0 + 180 % 180 === 0 → the window's first tick is due");
|
||||
assert.equal(card.glyph, "left", "the first classified turn ahead (progress 2000 < along 4000) is the 90° left");
|
||||
assert.equal(card.maneuverIdx, 1, "register: depart(00) → left(01)");
|
||||
const lines = card.caption.split("\n");
|
||||
assert.equal(lines.length, 4, "the anatomy is exactly four lines");
|
||||
assert.equal(lines[0], "GUIDE // T-01", "instrument register, zero-padded index");
|
||||
assert.equal(
|
||||
lines[1],
|
||||
"<b>667 M — TURN LEFT ONTO JALAN SULTAN ISMAIL</b>",
|
||||
"<b>2.0 KM — TURN LEFT ONTO JALAN SULTAN ISMAIL</b>",
|
||||
"maneuver line: street-bearing DEFAULT — {dist} — {verb} {STREET} (D-12)",
|
||||
);
|
||||
assert.equal(lines[2], "ETA 13:15 · 6.7 KM LEFT", "telemetry line from cardEtaTemplate: remaining 6667 m / 600 s");
|
||||
assert.equal(lines[2], "ETA 13:15 · 8.0 KM LEFT", "telemetry line from cardEtaTemplate: remaining 8000 m / 720 s");
|
||||
assert.equal(lines[3], strings.en.cardHonesty, "honesty line verbatim (D-13)");
|
||||
});
|
||||
|
||||
@@ -152,7 +152,7 @@ test("GC3 dead trip: elapsed ≥ duration → due false, no card — the cron ne
|
||||
test("GC4 caption law: ≤ 1024 chars, honesty verbatim, register zero-padded, no envelope string, D-13 banned words absent", async () => {
|
||||
const cron = await import("../src/guidance/cron.ts");
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 });
|
||||
const card = cron.projectTurnCards(env, T0 + 300, () => "Jalan Sultan Ismail");
|
||||
const card = cron.projectTurnCards(env, T0 + 180, () => "Jalan Sultan Ismail");
|
||||
assert.ok(card.caption.length <= 1024, "sendPhoto's caption budget is 1024 chars");
|
||||
assert.ok(card.caption.includes(strings.en.cardHonesty), "the honesty line rides verbatim");
|
||||
assert.match(card.caption, /^GUIDE \/\/ T-\d{2}\n/m, "the register is GUIDE // T-{zero-padded nn}");
|
||||
@@ -165,34 +165,34 @@ test("GC4 caption law: ≤ 1024 chars, honesty verbatim, register zero-padded, n
|
||||
test("GC5 street-less DEGRADATION: omitted resolver → the bare verb, same 4-line anatomy (honest absence, D-03)", async () => {
|
||||
const cron = await import("../src/guidance/cron.ts");
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 });
|
||||
const card = cron.projectTurnCards(env, T0 + 300); // no streetAt
|
||||
const card = cron.projectTurnCards(env, T0 + 180); // no streetAt
|
||||
assert.equal(card.due, true);
|
||||
assert.equal(card.glyph, "left");
|
||||
const lines = card.caption.split("\n");
|
||||
assert.equal(lines.length, 4, "the degraded form keeps the anatomy");
|
||||
assert.equal(lines[1], "<b>667 M — TURN LEFT</b>", "bare verb — street-less honest absence, never a guessed name");
|
||||
assert.equal(lines[1], "<b>2.0 KM — TURN LEFT</b>", "bare verb — street-less honest absence, never a guessed name");
|
||||
});
|
||||
|
||||
test("GC5b street resolver returning null → the bare verb too (a snap miss is honest absence)", async () => {
|
||||
const cron = await import("../src/guidance/cron.ts");
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 });
|
||||
const card = cron.projectTurnCards(env, T0 + 300, () => null);
|
||||
const card = cron.projectTurnCards(env, T0 + 180, () => null);
|
||||
const lines = card.caption.split("\n");
|
||||
assert.equal(lines[1], "<b>667 M — TURN LEFT</b>", "null street → the bare-verb degradation");
|
||||
assert.equal(lines[1], "<b>2.0 KM — TURN LEFT</b>", "null street → the bare-verb degradation");
|
||||
});
|
||||
|
||||
test("GC5c a 180° reversal carded as uturn — classifyTurn is the ONE turn authority", async () => {
|
||||
const cron = await import("../src/guidance/cron.ts");
|
||||
const p0 = { lat: KL_LAT, lng: KL_LNG };
|
||||
const p1 = { lat: KL_LAT, lng: KL_LNG + 2000 / M_PER_DEG_LNG }; // east, bearing 90
|
||||
const p1 = { lat: KL_LAT, lng: KL_LNG + 2500 / M_PER_DEG_LNG }; // east, bearing 90
|
||||
const p2 = { lat: KL_LAT, lng: KL_LNG }; // back west, bearing 270 → delta +180 → uturn
|
||||
const env = envelope({ points: [p0, p1, p2], distanceM: 4000, durationS: 360, startedAtSec: T0 });
|
||||
const card = cron.projectTurnCards(env, T0 + 90, () => "Jalan Sultan Ismail");
|
||||
const env = envelope({ points: [p0, p1, p2], distanceM: 5000, durationS: 400, startedAtSec: T0 });
|
||||
const card = cron.projectTurnCards(env, T0 + 180, () => "Jalan Sultan Ismail");
|
||||
assert.equal(card.glyph, "uturn", "delta 180 ≥ TURN_UTURN_MIN_DEG 150 → uturn");
|
||||
assert.equal(card.maneuverIdx, 1);
|
||||
assert.equal(
|
||||
card.caption.split("\n")[1],
|
||||
"<b>1000 M — MAKE A U-TURN ONTO JALAN SULTAN ISMAIL</b>",
|
||||
"<b>250 M — MAKE A U-TURN ONTO JALAN SULTAN ISMAIL</b>",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -295,13 +295,13 @@ test("GC7 overshoot: progress past the drawn geometry (below distanceM) → stra
|
||||
const cron = await import("../src/guidance/cron.ts");
|
||||
const points = threeLegPoints(); // drawn length ≈ 10000 m
|
||||
const env = envelope({ points, distanceM: 20000, durationS: 900, startedAtSec: T0 });
|
||||
const card = cron.projectTurnCards(env, T0 + 675, () => "Jalan Sultan Ismail"); // progress 15000 > geometry
|
||||
assert.equal(card.due, true);
|
||||
const card = cron.projectTurnCards(env, T0 + 720, () => "Jalan Sultan Ismail"); // progress 16000 > drawn
|
||||
assert.equal(card.due, true, "T0 + 720 % 180 === 0 → due");
|
||||
assert.equal(card.glyph, "straight", "no maneuver ahead on the drawn line → the straight fallback");
|
||||
assert.equal(card.maneuverIdx, 0, "the fallback register index is 00");
|
||||
const lines = card.caption.split("\n");
|
||||
assert.equal(lines[0], "GUIDE // T-00");
|
||||
assert.equal(lines[1], "<b>5.0 KM — CONTINUE ON JALAN SULTAN ISMAIL</b>", "the honest remaining trip distance is the line's number");
|
||||
assert.equal(lines[1], "<b>4.0 KM — CONTINUE ON JALAN SULTAN ISMAIL</b>", "the honest remaining trip distance is the line's number");
|
||||
});
|
||||
|
||||
/* ————— GC8 — structural laws (comment-stripped source introspection) ————— */
|
||||
|
||||
Reference in New Issue
Block a user