fix(07-06): launch-gate decisions — KV cron retune to 720/day (the $0 law holds), brand sweep araharah→arah² across user-facing copy, menu button text
This commit is contained in:
@@ -10,7 +10,9 @@
|
||||
* Pure-function block (task 1):
|
||||
* GC1 projection math — T+300 of a 900 s / 10 km snapshot → the 90° left
|
||||
* carded at register T-01, "667 M", ETA 13:15 · 6.7 KM LEFT
|
||||
* GC2 cadence table — CARD_CADENCE_S 180; due on the first tick of each
|
||||
* GC2 cadence table — CARD_CADENCE_S 240 (launch-gate retune, 2025-10-25:
|
||||
* 2-minute cron ticks x 1 list-op = 720/day, under the 1,000/day free
|
||||
* ceiling — the $0 law); due on the first tick of each
|
||||
* 180 s window (nowSec % 180 < 60); non-due ticks carry no card
|
||||
* GC3 dead trip — elapsed ≥ duration (progress clamped ≥ distanceM) →
|
||||
* due false, no card: the cron never cards a dead trip
|
||||
@@ -96,11 +98,11 @@ function envelope({ points, distanceM, durationS, startedAtSec, chatId = 101 })
|
||||
|
||||
/* ————— GC1 — the projection math (fixed nowSec, fake street resolver) ————— */
|
||||
|
||||
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 () => {
|
||||
test("GC1 projection: elapsed 180 s at the retuned due tick (900 s total) → the 90° left carded at T-01, 2.0 KM, ETA 13:16 · 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 + 180, () => "Jalan Sultan Ismail");
|
||||
assert.equal(card.due, true, "T0 + 180 % 180 === 0 → the window's first tick is due");
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 + 60 });
|
||||
const card = cron.projectTurnCards(env, T0 + 240, () => "Jalan Sultan Ismail");
|
||||
assert.equal(card.due, true, "T0 + 240 % 240 === 0 → the window's first tick is due (the 240 s retune)");
|
||||
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");
|
||||
@@ -111,20 +113,20 @@ test("GC1 projection: elapsed 180 s of 900 → the 90° left carded at T-01, 2.0
|
||||
"<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 · 8.0 KM LEFT", "telemetry line from cardEtaTemplate: remaining 8000 m / 720 s");
|
||||
assert.equal(lines[2], "ETA 13:16 · 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)");
|
||||
});
|
||||
|
||||
/* ————— GC2 — the cadence table ————— */
|
||||
|
||||
test("GC2 cadence: CARD_CADENCE_S 180; due true on the window's first ticks, false after, no card on a non-due tick", async () => {
|
||||
test("GC2 cadence: CARD_CADENCE_S 240 (*/2-tick retune); due true on the window's first ticks, false after, no card on a non-due tick", async () => {
|
||||
const cron = await import("../src/guidance/cron.ts");
|
||||
assert.equal(cron.CARD_CADENCE_S, 180, "the 3-minute cadence is an exported named constant");
|
||||
assert.equal(cron.CARD_CADENCE_S, 240, "the 4-minute card cadence is an exported named constant (launch-gate retune: */2 cron + 240 s = 720 list-ops/day, under the 1,000 ceiling)");
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 });
|
||||
const table = [[3600, true], [3659, true], [3660, false], [3779, false], [3780, true]];
|
||||
const table = [[3600, true], [3659, true], [3660, false], [3779, false], [3840, true], [3899, true], [3900, false], [4079, false], [4080, true]];
|
||||
for (const [nowSec, expectedDue] of table) {
|
||||
const card = cron.projectTurnCards(env, nowSec);
|
||||
assert.equal(card.due, expectedDue, `nowSec ${nowSec}: nowSec % 180 < 60 → ${expectedDue}`);
|
||||
assert.equal(card.due, expectedDue, `nowSec ${nowSec}: nowSec % 240 < 60 → ${expectedDue}`);
|
||||
if (!expectedDue) {
|
||||
assert.equal(card.caption, null, "a non-due tick produces no card");
|
||||
assert.equal(card.glyph, null, "a non-due tick produces no glyph");
|
||||
@@ -136,7 +138,7 @@ test("GC2 cadence: CARD_CADENCE_S 180; due true on the window's first ticks, fal
|
||||
|
||||
test("GC3 dead trip: elapsed ≥ duration → due false, no card — the cron never cards a dead trip", async () => {
|
||||
const cron = await import("../src/guidance/cron.ts");
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 });
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 + 60 });
|
||||
for (const nowSec of [T0 + 900, T0 + 901, T0 + 5000]) {
|
||||
const card = cron.projectTurnCards(env, nowSec);
|
||||
assert.equal(card.due, false, `elapsed ${nowSec - T0} s ≥ duration 900 s → the trip would have ended`);
|
||||
@@ -152,7 +154,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 + 180, () => "Jalan Sultan Ismail");
|
||||
const card = cron.projectTurnCards(env, T0 + 240, () => "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}");
|
||||
@@ -164,8 +166,8 @@ 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 + 180); // no streetAt
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 + 60 });
|
||||
const card = cron.projectTurnCards(env, T0 + 240); // no streetAt
|
||||
assert.equal(card.due, true);
|
||||
assert.equal(card.glyph, "left");
|
||||
const lines = card.caption.split("\n");
|
||||
@@ -175,8 +177,8 @@ test("GC5 street-less DEGRADATION: omitted resolver → the bare verb, same 4-li
|
||||
|
||||
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 + 180, () => null);
|
||||
const env = envelope({ points: threeLegPoints(), distanceM: 10000, durationS: 900, startedAtSec: T0 + 60 });
|
||||
const card = cron.projectTurnCards(env, T0 + 240, () => null);
|
||||
const lines = card.caption.split("\n");
|
||||
assert.equal(lines[1], "<b>2.0 KM — TURN LEFT</b>", "null street → the bare-verb degradation");
|
||||
});
|
||||
@@ -186,8 +188,8 @@ test("GC5c a 180° reversal carded as uturn — classifyTurn is the ONE turn aut
|
||||
const p0 = { lat: KL_LAT, lng: KL_LNG };
|
||||
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: 5000, durationS: 400, startedAtSec: T0 });
|
||||
const card = cron.projectTurnCards(env, T0 + 180, () => "Jalan Sultan Ismail");
|
||||
const env = envelope({ points: [p0, p1, p2], distanceM: 5000, durationS: 400, startedAtSec: T0 + 60 });
|
||||
const card = cron.projectTurnCards(env, T0 + 240, () => "Jalan Sultan Ismail");
|
||||
assert.equal(card.glyph, "uturn", "delta 180 ≥ TURN_UTURN_MIN_DEG 150 → uturn");
|
||||
assert.equal(card.maneuverIdx, 1);
|
||||
assert.equal(
|
||||
@@ -434,7 +436,7 @@ async function sentPhotos(calls) {
|
||||
return out;
|
||||
}
|
||||
|
||||
const TICK_SEC = T0 + 180; // % 180 === 0 → a due tick
|
||||
const TICK_SEC = T0 + 240; // % 240 === 0 → a due tick (the launch-gate retune)
|
||||
|
||||
test("GC-C1 due tick: the alive envelope's card sends to ITS OWN chat, street-bearing from the mini-graph R2 fake", async () => {
|
||||
const mod = await import("../src/webhook.ts");
|
||||
|
||||
Reference in New Issue
Block a user