fix: a chapter's first mile-marker post no longer arrives late

This commit is contained in:
2026-10-11 16:12:15 +03:00
parent 388289f2fe
commit 8c9ec69565
3 changed files with 119 additions and 9 deletions

View File

@@ -9,6 +9,8 @@ import {
MILESTONE_ASSIGN_Z,
MILESTONE_FAR_Z,
MILESTONE_FLASH_MAX_RATE,
MILESTONE_LATE_ASSIGN_Z,
MILESTONE_NEAR_Z,
MILESTONE_PASS_Z,
MILESTONE_SIDE,
@@ -200,7 +202,53 @@ describe('mile markers on the road', () => {
expect(seen.filter((pass) => pass.reverse).map((pass) => pass.entry)).toEqual([2, 1, 0]);
});
it('only hands an entry to a post still far up the road, in the haze', () => {
it('does not keep the first entry waiting when the reader starts scrolling at the heading', () => {
for (const [section, rate, speed] of [
['profile', 0.03, 18],
['profile', 0.05, 18],
['experience', 0.03, 18],
['experience', 0.05, 25],
] as const) {
const count = MILESTONE_COUNTS[section];
for (const lead of [0, 0.4, 1.7, 2.9]) {
const road = createMilestoneRoad(true);
// Cruising through the chapter before, then straight into reading from the heading.
const state = drive(road, 3 + lead, () => ({ section: 'start', p: null, speed }));
const seen: Array<{ entry: number; p: number }> = [];
let last: readonly number[] = road.posts().map((post) => post.z);
drive(road, 1 / rate, (t) => ({ section, p: Math.min(1, t * rate), speed }), state, (posts, _t, p) => {
posts.forEach((post, k) => {
if (post.entry >= 0 && post.presence > 0 && last[k]! < MILESTONE_PASS_Z && post.z >= MILESTONE_PASS_Z) {
seen.push({ entry: post.entry, p: p! });
}
});
last = posts.map((post) => post.z);
});
expect(seen[0]?.entry).toBe(0);
// Within a post's spacing of the line (the road brings one every 60 m); it used to be up to two entries late.
expect(Math.abs(seen[0]!.p - passPoint(0, count))).toBeLessThan(0.4 / count);
}
}
});
it('sends the opening entry by soon when the reader stays at the heading', () => {
for (const lead of [0, 0.4, 1.7, 2.9]) {
const road = createMilestoneRoad(true);
const state = drive(road, 3 + lead, () => ({ section: 'start', p: null, speed: 18 }));
let first = Number.POSITIVE_INFINITY;
let last: readonly number[] = road.posts().map((post) => post.z);
drive(road, 12, () => ({ section: 'profile', p: 0, speed: 18 }), state, (posts, t) => {
posts.forEach((post, k) => {
if (post.entry === 0 && post.presence > 0 && last[k]! < MILESTONE_PASS_Z && post.z >= MILESTONE_PASS_Z) first = Math.min(first, t);
});
last = posts.map((post) => post.z);
});
// A post already on the road, not one from the haze (~8.5 s at this speed).
expect(first).toBeLessThan(6.5);
}
});
it('only hands an entry to a post still far up the road, or to a dark one as a chapter begins', () => {
const road = createMilestoneRoad(true);
const born = new Map<number, number>();
drive(road, 30, (t) => ({ section: 'experience', p: Math.min(1, t * 0.03), speed: 30 }), { distance: 0 }, (posts) => {
@@ -209,7 +257,10 @@ describe('mile markers on the road', () => {
});
});
expect(born.size).toBeGreaterThan(0);
for (const z of born.values()) expect(z).toBeLessThan(MILESTONE_ASSIGN_Z);
// The opening entry may go to a nearer post that was still dark; every later one comes from the haze.
const zs = [...born.values()];
expect(zs[0]!).toBeLessThan(MILESTONE_LATE_ASSIGN_Z);
for (const z of zs.slice(1)) expect(z).toBeLessThan(MILESTONE_ASSIGN_Z);
});
});