zenno: lint fixes
ci/woodpecker/push/horsebot Pipeline failed
ci/woodpecker/push/zenno Pipeline was successful

This commit is contained in:
2026-10-31 13:23:15 -04:00
parent 5a2b9b7bf4
commit 04be0b75a9
7 changed files with 228 additions and 166 deletions
+1 -2
View File
@@ -18,11 +18,10 @@
title?: string; title?: string;
class?: ClassValue | null; class?: ClassValue | null;
plotOptions?: Omit<Plot.PlotOptions, 'marks' | 'x' | 'y'>; plotOptions?: Omit<Plot.PlotOptions, 'marks' | 'x' | 'y'>;
fontColor?: string;
draw: DrawSpan[]; draw: DrawSpan[];
} }
let { course, title, class: className, plotOptions, fontColor, draw }: Props = $props(); let { course, title, class: className, plotOptions, draw }: Props = $props();
let width = $state(0); let width = $state(0);
let height = $state(0); let height = $state(0);
+1 -1
View File
@@ -109,7 +109,7 @@ export const PAGES = {
{ {
route: resolve('/doc/fans'), route: resolve('/doc/fans'),
name: 'Career Fan Gain Is Solved', name: 'Career Fan Gain Is Solved',
description: 'The exact formula for fan gain from career races – nothing "random" about it.' description: 'The exact formula for fan gain from career races – nothing "random" about it.',
}, },
] satisfies Page[], ] satisfies Page[],
} as const; } as const;
+77 -77
View File
@@ -2,81 +2,81 @@ import { expect, test } from 'vitest';
import * as condition from './condition'; import * as condition from './condition';
test.for([ test.for([
{ {
c: '', c: '',
want: [], want: [],
}, },
{ {
c: 'all_corner_random==1', c: 'all_corner_random==1',
want: [[{ type: 'all_corner_random', op: '==', arg: 1 }]], want: [[{ type: 'all_corner_random', op: '==', arg: 1 }]],
}, },
{ {
c: 'corner!=0', c: 'corner!=0',
want: [[{ type: 'corner', op: '!=', arg: 0 }]], want: [[{ type: 'corner', op: '!=', arg: 0 }]],
}, },
{ {
c: 'order_rate<80', c: 'order_rate<80',
want: [[{ type: 'order_rate', op: '<', arg: 80 }]], want: [[{ type: 'order_rate', op: '<', arg: 80 }]],
}, },
{ {
c: 'distance_diff_top<=5', c: 'distance_diff_top<=5',
want: [[{ type: 'distance_diff_top', op: '<=', arg: 5 }]], want: [[{ type: 'distance_diff_top', op: '<=', arg: 5 }]],
}, },
{ {
c: 'motivation>=4', c: 'motivation>=4',
want: [[{ type: 'motivation', op: '>=', arg: 4 }]], want: [[{ type: 'motivation', op: '>=', arg: 4 }]],
}, },
{ {
c: 'compete_fight_count>0', c: 'compete_fight_count>0',
want: [[{ type: 'compete_fight_count', op: '>', arg: 0 }]], want: [[{ type: 'compete_fight_count', op: '>', arg: 0 }]],
}, },
{ {
c: 'phase==1&change_order_onetime<0', c: 'phase==1&change_order_onetime<0',
want: [ want: [
[ [
{ type: 'phase', op: '==', arg: 1 }, { type: 'phase', op: '==', arg: 1 },
{ type: 'change_order_onetime', op: '<', arg: 0 }, { type: 'change_order_onetime', op: '<', arg: 0 },
], ],
], ],
}, },
{ {
c: 'temptation_count==0&remain_distance<=201&remain_distance>=199&order>=4&order_rate<=70', c: 'temptation_count==0&remain_distance<=201&remain_distance>=199&order>=4&order_rate<=70',
want: [ want: [
[ [
{ type: 'temptation_count', op: '==', arg: 0 }, { type: 'temptation_count', op: '==', arg: 0 },
{ type: 'remain_distance', op: '<=', arg: 201 }, { type: 'remain_distance', op: '<=', arg: 201 },
{ type: 'remain_distance', op: '>=', arg: 199 }, { type: 'remain_distance', op: '>=', arg: 199 },
{ type: 'order', op: '>=', arg: 4 }, { type: 'order', op: '>=', arg: 4 },
{ type: 'order_rate', op: '<=', arg: 70 }, { type: 'order_rate', op: '<=', arg: 70 },
], ],
], ],
}, },
{ {
c: 'track_id==10001@track_id==10002@track_id==10004@track_id==10010@track_id==10103@track_id==10104', c: 'track_id==10001@track_id==10002@track_id==10004@track_id==10010@track_id==10103@track_id==10104',
want: [ want: [
[{ type: 'track_id', op: '==', arg: 10001 }], [{ type: 'track_id', op: '==', arg: 10001 }],
[{ type: 'track_id', op: '==', arg: 10002 }], [{ type: 'track_id', op: '==', arg: 10002 }],
[{ type: 'track_id', op: '==', arg: 10004 }], [{ type: 'track_id', op: '==', arg: 10004 }],
[{ type: 'track_id', op: '==', arg: 10010 }], [{ type: 'track_id', op: '==', arg: 10010 }],
[{ type: 'track_id', op: '==', arg: 10103 }], [{ type: 'track_id', op: '==', arg: 10103 }],
[{ type: 'track_id', op: '==', arg: 10104 }], [{ type: 'track_id', op: '==', arg: 10104 }],
], ],
}, },
{ {
c: 'distance_type==1&phase>=2&order_rate>=50&is_overtake==1@distance_type==2&phase>=2&order_rate>=50&is_overtake==1', c: 'distance_type==1&phase>=2&order_rate>=50&is_overtake==1@distance_type==2&phase>=2&order_rate>=50&is_overtake==1',
want: [ want: [
[ [
{ type: 'distance_type', op: '==', arg: 1 }, { type: 'distance_type', op: '==', arg: 1 },
{ type: 'phase', op: '>=', arg: 2 }, { type: 'phase', op: '>=', arg: 2 },
{ type: 'order_rate', op: '>=', arg: 50 }, { type: 'order_rate', op: '>=', arg: 50 },
{ type: 'is_overtake', op: '==', arg: 1 }, { type: 'is_overtake', op: '==', arg: 1 },
], ],
[ [
{ type: 'distance_type', op: '==', arg: 2 }, { type: 'distance_type', op: '==', arg: 2 },
{ type: 'phase', op: '>=', arg: 2 }, { type: 'phase', op: '>=', arg: 2 },
{ type: 'order_rate', op: '>=', arg: 50 }, { type: 'order_rate', op: '>=', arg: 50 },
{ type: 'is_overtake', op: '==', arg: 1 }, { type: 'is_overtake', op: '==', arg: 1 },
], ],
], ],
}, },
])('$c', ({ c, want }) => expect(condition.parse(c)).toEqual(want)); ])('$c', ({ c, want }) => expect(condition.parse(c)).toEqual(want));
+144 -81
View File
@@ -55,17 +55,17 @@ function phase(course: racecourse.Course, op: Op, phase: race.Phase, type: SpanT
if (phase === race.Phase.EarlyRace) { if (phase === race.Phase.EarlyRace) {
return { start: 0, end: p[race.Phase.EarlyRace], type }; return { start: 0, end: p[race.Phase.EarlyRace], type };
} }
return { start: p[phase - 1 as race.Phase], end: p[phase], type }; return { start: p[(phase - 1) as race.Phase], end: p[phase], type };
case Op.Le: case Op.Le:
return { start: 0, end: p[phase], type }; return { start: 0, end: p[phase], type };
case Op.Ge: case Op.Ge:
if (phase === race.Phase.EarlyRace) { if (phase === race.Phase.EarlyRace) {
return { start: 0, end: course.distance, type }; return { start: 0, end: course.distance, type };
} }
return { start: p[phase - 1 as race.Phase], end: course.distance, type }; return { start: p[(phase - 1) as race.Phase], end: course.distance, type };
default: default:
// Other operators aren't used. // Other operators aren't used.
throw new Error("weird operator on phase condition"); throw new Error('weird operator on phase condition');
} }
} }
@@ -90,10 +90,10 @@ function distrate(course: racecourse.Course, cond: Condition, type: SpanType): S
switch (cond.op) { switch (cond.op) {
case Op.Le: case Op.Le:
case Op.Lt: case Op.Lt:
return { start: 0, end: Math.round(course.distance * cond.arg / 100), type }; return { start: 0, end: Math.round((course.distance * cond.arg) / 100), type };
case Op.Ge: case Op.Ge:
case Op.Gt: case Op.Gt:
return { start: Math.round(course.distance * cond.arg / 100), end: course.distance, type }; return { start: Math.round((course.distance * cond.arg) / 100), end: course.distance, type };
} }
return { start: 0, end: 0, type: SpanType.Never }; return { start: 0, end: 0, type: SpanType.Never };
} }
@@ -103,7 +103,7 @@ function remaining(course: racecourse.Course, cond: Condition, type: SpanType):
case Op.Eq: case Op.Eq:
return { start: course.distance - cond.arg - 1, end: course.distance - cond.arg + 1, type }; return { start: course.distance - cond.arg - 1, end: course.distance - cond.arg + 1, type };
case Op.Ne: case Op.Ne:
throw new Error("weird operator on remaining distance"); throw new Error('weird operator on remaining distance');
case Op.Le: case Op.Le:
case Op.Lt: case Op.Lt:
return { start: course.distance - cond.arg, end: course.distance, type }; return { start: course.distance - cond.arg, end: course.distance, type };
@@ -169,33 +169,58 @@ function must(x: boolean): SpanType {
} }
const perCond: Readonly<Partial<Record<Condition['type'], (course: racecourse.Course, cond: Condition) => Span[]>>> = { const perCond: Readonly<Partial<Record<Condition['type'], (course: racecourse.Course, cond: Condition) => Span[]>>> = {
'accumulatetime': (course, cond) => [accumulatetime(course, cond.arg, SpanType.Whenever)], accumulatetime: (course, cond) => [accumulatetime(course, cond.arg, SpanType.Whenever)],
'activate_count_all_team': (course) => [{ start: 0, end: course.distance, type: SpanType.Skill }], activate_count_all_team: (course) => [{ start: 0, end: course.distance, type: SpanType.Skill }],
'activate_count_end_after': (course) => [endafter(course, SpanType.Skill)], activate_count_end_after: (course) => [endafter(course, SpanType.Skill)],
'activate_count_heal': (course) => [{ start: 0, end: course.distance, type: SpanType.Skill }], activate_count_heal: (course) => [{ start: 0, end: course.distance, type: SpanType.Skill }],
'activate_count_later_half': (course) => [{ start: course.distance / 2, end: course.distance, type: SpanType.Skill }], activate_count_later_half: (course) => [{ start: course.distance / 2, end: course.distance, type: SpanType.Skill }],
'activate_count_middle': (course) => [phase(course, Op.Eq, race.Phase.MidRace, SpanType.Skill)], activate_count_middle: (course) => [phase(course, Op.Eq, race.Phase.MidRace, SpanType.Skill)],
'activate_count_start': (course) => [phase(course, Op.Eq, race.Phase.EarlyRace, SpanType.Skill)], activate_count_start: (course) => [phase(course, Op.Eq, race.Phase.EarlyRace, SpanType.Skill)],
'all_corner_random': (course) => coursespans(course.spans.filter((s) => s.corner != null), SpanType.Random), all_corner_random: (course) =>
'change_order_up_end_after': (course) => [endafter(course, SpanType.Whenever)], coursespans(
'change_order_up_finalcorner_after': (course) => [is_finalcorner(course, true, SpanType.Whenever)], course.spans.filter((s) => s.corner != null),
'change_order_up_middle': (course) => [phase(course, Op.Eq, race.Phase.MidRace, SpanType.Whenever)], SpanType.Random,
'compete_fight_count': (course) => [laststraight(course, SpanType.Whenever)], ),
'corner': (course, cond) => coursespans(course.spans.filter((s) => opcompare(cond, s.corner ?? 0)), SpanType.Whenever), change_order_up_end_after: (course) => [endafter(course, SpanType.Whenever)],
'corner_count': (course, cond) => [{ start: 0, end: course.distance, type: opcompare(cond, course.spans.reduce((n, s) => n + (s.corner != null ? 1 : 0), 0)) ? SpanType.Never : SpanType.Whenever }], change_order_up_finalcorner_after: (course) => [is_finalcorner(course, true, SpanType.Whenever)],
'corner_random': (course, cond) => { change_order_up_middle: (course) => [phase(course, Op.Eq, race.Phase.MidRace, SpanType.Whenever)],
compete_fight_count: (course) => [laststraight(course, SpanType.Whenever)],
corner: (course, cond) =>
coursespans(
course.spans.filter((s) => opcompare(cond, s.corner ?? 0)),
SpanType.Whenever,
),
corner_count: (course, cond) => [
{
start: 0,
end: course.distance,
type: opcompare(
cond,
course.spans.reduce((n, s) => n + (s.corner != null ? 1 : 0), 0),
)
? SpanType.Never
: SpanType.Whenever,
},
],
corner_random: (course, cond) => {
const c = course.spans.findLast((s) => s.corner === cond.arg); const c = course.spans.findLast((s) => s.corner === cond.arg);
if (c == null) { if (c == null) {
return [{ start: 0, end: 0, type: SpanType.Never }]; return [{ start: 0, end: 0, type: SpanType.Never }];
} }
return [{ start: c.start, end: c.end, type: SpanType.Random }]; return [{ start: c.start, end: c.end, type: SpanType.Random }];
}, },
'course_distance': (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.distance)) }], course_distance: (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.distance)) }],
'distance_rate': (course, cond) => [distrate(course, cond, SpanType.Whenever)], distance_rate: (course, cond) => [distrate(course, cond, SpanType.Whenever)],
'distance_rate_after_random': (course, cond) => [distrate(course, cond, SpanType.Random)], distance_rate_after_random: (course, cond) => [distrate(course, cond, SpanType.Random)],
'distance_type': (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, racecourse.distanceType(course))) }], distance_type: (course, cond) => [
'down_slope_random': (course) => coursespans(course.slopes.filter((s) => s.per < 0), SpanType.Random), { start: 0, end: course.distance, type: must(opcompare(cond, racecourse.distanceType(course))) },
'furlong': (course, cond) => { ],
down_slope_random: (course) =>
coursespans(
course.slopes.filter((s) => s.per < 0),
SpanType.Random,
),
furlong: (course, cond) => {
switch (cond.op) { switch (cond.op) {
case Op.Eq: case Op.Eq:
return [{ start: cond.arg * 201.168, end: (cond.arg + 1) * 201.168, type: SpanType.Whenever }]; return [{ start: cond.arg * 201.168, end: (cond.arg + 1) * 201.168, type: SpanType.Whenever }];
@@ -212,9 +237,9 @@ const perCond: Readonly<Partial<Record<Condition['type'], (course: racecourse.Co
return [{ start: cond.arg * 201.168, end: course.distance, type: SpanType.Whenever }]; return [{ start: cond.arg * 201.168, end: course.distance, type: SpanType.Whenever }];
} }
}, },
'ground_type': (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.ground)) }], ground_type: (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.ground)) }],
// hp_per would be an interesting challenge... // hp_per would be an interesting challenge...
'is_finalcorner': (course, cond) => { is_finalcorner: (course, cond) => {
// Either is_finalcorner==1 or is_finalcorner==0. // Either is_finalcorner==1 or is_finalcorner==0.
if (cond.arg === 1) { if (cond.arg === 1) {
return [is_finalcorner(course, true, SpanType.Whenever)]; return [is_finalcorner(course, true, SpanType.Whenever)];
@@ -226,84 +251,122 @@ const perCond: Readonly<Partial<Record<Condition['type'], (course: racecourse.Co
return [{ start: 0, end: c.start, type: SpanType.Whenever }]; return [{ start: 0, end: c.start, type: SpanType.Whenever }];
}, },
// TODO(zeph): is_abroad // TODO(zeph): is_abroad
'is_activate_any_skill': (course) => [{ start: 0, end: course.distance, type: SpanType.Skill }], is_activate_any_skill: (course) => [{ start: 0, end: course.distance, type: SpanType.Skill }],
'is_activate_heal_skill': (course) => [{ start: 0, end: course.distance, type: SpanType.Skill }], is_activate_heal_skill: (course) => [{ start: 0, end: course.distance, type: SpanType.Skill }],
'is_basis_distance': (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.distance % 400 === 0 ? 1 : 0)) }], is_basis_distance: (course, cond) => [
'is_dirtgrade': (course, cond) => { { start: 0, end: course.distance, type: must(opcompare(cond, course.distance % 400 === 0 ? 1 : 0)) },
],
is_dirtgrade: (course, cond) => {
// NAR tracks: Ooi, Kawasaki, Funabashi, Morioka // NAR tracks: Ooi, Kawasaki, Funabashi, Morioka
const is = [10101, 10103, 10104, 10105].includes(course.race_track_id); const is = [10101, 10103, 10104, 10105].includes(course.race_track_id);
return [{ start: 0, end: course.distance, type: must(opcompare(cond, is ? 1 : 0)) }]; return [{ start: 0, end: course.distance, type: must(opcompare(cond, is ? 1 : 0)) }];
}, },
'is_finalcorner_laterhalf': (course) => [later(is_finalcorner(course, false, SpanType.Whenever), 0.5)], is_finalcorner_laterhalf: (course) => [later(is_finalcorner(course, false, SpanType.Whenever), 0.5)],
'is_finalcorner_random': (course) => [is_finalcorner(course, false, SpanType.Random)], is_finalcorner_random: (course) => [is_finalcorner(course, false, SpanType.Random)],
'is_last_straight': (course) => [laststraight(course, SpanType.Whenever)], is_last_straight: (course) => [laststraight(course, SpanType.Whenever)],
'is_last_straight_onetime': (course) => [first(laststraight(course, SpanType.Whenever), 0.02)], is_last_straight_onetime: (course) => [first(laststraight(course, SpanType.Whenever), 0.02)],
'is_lastspurt': (course) => [endafter(course, SpanType.Whenever)], is_lastspurt: (course) => [endafter(course, SpanType.Whenever)],
// could implement is_temptation!=0, but that condition isn't used // could implement is_temptation!=0, but that condition isn't used
'is_tight_track': (course, cond) => { is_tight_track: (course, cond) => {
// Sapporo, Hakodate, Fukushima, Kokura, Kawasaki, Funabashi // Sapporo, Hakodate, Fukushima, Kokura, Kawasaki, Funabashi
// TODO(zeph): GameTora says "afaik" for this list... // TODO(zeph): GameTora says "afaik" for this list...
const is = [10001, 10002, 10004, 10010, 10103, 10104]; const is = [10001, 10002, 10004, 10010, 10103, 10104];
return [{ start: 0, end: course.distance, type: must(opcompare(cond, is ? 1 : 0)) }]; return [{ start: 0, end: course.distance, type: must(opcompare(cond, is ? 1 : 0)) }];
}, },
'last_straight_random': (course) => [laststraight(course, SpanType.Random)], last_straight_random: (course) => [laststraight(course, SpanType.Random)],
'lastspurt': (course) => [endafter(course, SpanType.Whenever)], lastspurt: (course) => [endafter(course, SpanType.Whenever)],
'order_rate_in20_continue': (course) => [accumulatetime(course, 5, SpanType.Whenever)], order_rate_in20_continue: (course) => [accumulatetime(course, 5, SpanType.Whenever)],
'order_rate_in40_continue': (course) => [accumulatetime(course, 5, SpanType.Whenever)], order_rate_in40_continue: (course) => [accumulatetime(course, 5, SpanType.Whenever)],
'order_rate_in50_continue': (course) => [accumulatetime(course, 5, SpanType.Whenever)], order_rate_in50_continue: (course) => [accumulatetime(course, 5, SpanType.Whenever)],
'order_rate_in80_continue': (course) => [accumulatetime(course, 5, SpanType.Whenever)], order_rate_in80_continue: (course) => [accumulatetime(course, 5, SpanType.Whenever)],
'order_rate_out20_continue': (course) => [accumulatetime(course, 5, SpanType.Whenever)], order_rate_out20_continue: (course) => [accumulatetime(course, 5, SpanType.Whenever)],
'order_rate_out40_continue': (course) => [accumulatetime(course, 5, SpanType.Whenever)], order_rate_out40_continue: (course) => [accumulatetime(course, 5, SpanType.Whenever)],
'order_rate_out50_continue': (course) => [accumulatetime(course, 5, SpanType.Whenever)], order_rate_out50_continue: (course) => [accumulatetime(course, 5, SpanType.Whenever)],
'order_rate_out70_continue': (course) => [accumulatetime(course, 5, SpanType.Whenever)], order_rate_out70_continue: (course) => [accumulatetime(course, 5, SpanType.Whenever)],
'phase': (course, cond) => [phase(course, cond.op, cond.arg, SpanType.Whenever)], phase: (course, cond) => [phase(course, cond.op, cond.arg, SpanType.Whenever)],
'phase_corner_random': (course, cond) => { phase_corner_random: (course, cond) => {
const p = phase(course, Op.Eq, cond.arg, SpanType.Random); const p = phase(course, Op.Eq, cond.arg, SpanType.Random);
return coursespans(course.spans.filter((s) => s.corner != null), SpanType.Random).map((s) => intersection(s, p)); return coursespans(
course.spans.filter((s) => s.corner != null),
SpanType.Random,
).map((s) => intersection(s, p));
}, },
'phase_first_half_straight_random': (course, cond) => { phase_first_half_straight_random: (course, cond) => {
const p = first(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.5); const p = first(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.5);
return coursespans(course.spans.filter((s) => s.straight != null), SpanType.Random).map((s) => intersection(s, p)); return coursespans(
course.spans.filter((s) => s.straight != null),
SpanType.Random,
).map((s) => intersection(s, p));
}, },
'phase_firsthalf': (course, cond) => [first(phase(course, Op.Eq, cond.arg, SpanType.Whenever), 0.5)], phase_firsthalf: (course, cond) => [first(phase(course, Op.Eq, cond.arg, SpanType.Whenever), 0.5)],
'phase_firsthalf_random': (course, cond) => [first(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.5)], phase_firsthalf_random: (course, cond) => [first(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.5)],
'phase_firstquarter': (course, cond) => [first(phase(course, Op.Eq, cond.arg, SpanType.Whenever), 0.25)], phase_firstquarter: (course, cond) => [first(phase(course, Op.Eq, cond.arg, SpanType.Whenever), 0.25)],
'phase_firstquarter_random': (course, cond) => [first(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.25)], phase_firstquarter_random: (course, cond) => [first(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.25)],
'phase_laterhalf': (course, cond) => [later(phase(course, Op.Eq, cond.arg, SpanType.Whenever), 0.5)], phase_laterhalf: (course, cond) => [later(phase(course, Op.Eq, cond.arg, SpanType.Whenever), 0.5)],
'phase_laterhalf_random': (course, cond) => [later(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.5)], phase_laterhalf_random: (course, cond) => [later(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.5)],
'phase_latter_half_straight_random': (course, cond) => { phase_latter_half_straight_random: (course, cond) => {
const p = later(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.5); const p = later(phase(course, Op.Eq, cond.arg, SpanType.Random), 0.5);
return coursespans(course.spans.filter((s) => s.straight != null), SpanType.Random).map((s) => intersection(s, p)); return coursespans(
course.spans.filter((s) => s.straight != null),
SpanType.Random,
).map((s) => intersection(s, p));
}, },
'phase_random': (course, cond) => [phase(course, cond.op, cond.arg, SpanType.Random)], phase_random: (course, cond) => [phase(course, cond.op, cond.arg, SpanType.Random)],
'phase_straight_random': (course, cond) => { phase_straight_random: (course, cond) => {
const p = phase(course, Op.Eq, cond.arg, SpanType.Random); const p = phase(course, Op.Eq, cond.arg, SpanType.Random);
return coursespans(course.spans.filter((s) => s.straight != null), SpanType.Random).map((s) => intersection(s, p)); return coursespans(
course.spans.filter((s) => s.straight != null),
SpanType.Random,
).map((s) => intersection(s, p));
}, },
'remain_distance': (course, cond) => [remaining(course, cond, SpanType.Whenever)], remain_distance: (course, cond) => [remaining(course, cond, SpanType.Whenever)],
'remain_distance_viewer_id': (course, cond) => [remaining(course, cond, SpanType.Whenever)], remain_distance_viewer_id: (course, cond) => [remaining(course, cond, SpanType.Whenever)],
'rotation': (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.turn)) }], rotation: (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.turn)) }],
'run_at_full_speed_random': (course) => [endafter(course, SpanType.Random)], run_at_full_speed_random: (course) => [endafter(course, SpanType.Random)],
'slope': (course, cond) => { slope: (course, cond) => {
switch (cond.arg) { switch (cond.arg) {
case 0: case 0:
return coursespans(racecourse.fillSlopes(course.slopes, course.distance).filter((s) => s.per === 0), SpanType.Whenever); return coursespans(
racecourse.fillSlopes(course.slopes, course.distance).filter((s) => s.per === 0),
SpanType.Whenever,
);
case 1: case 1:
return coursespans(course.slopes.filter((s) => s.per > 0), SpanType.Whenever); return coursespans(
course.slopes.filter((s) => s.per > 0),
SpanType.Whenever,
);
case 2: case 2:
return coursespans(course.slopes.filter((s) => s.per < 0), SpanType.Whenever); return coursespans(
course.slopes.filter((s) => s.per < 0),
SpanType.Whenever,
);
default: default:
throw new Error("weird argument to slope condition"); throw new Error('weird argument to slope condition');
} }
}, },
'straight_front_type': (course, cond) => coursespans(course.spans.filter((s) => s.straight === cond.arg), SpanType.Whenever), straight_front_type: (course, cond) =>
'straight_random': (course) => coursespans(course.spans.filter((s) => s.straight != null), SpanType.Random), coursespans(
'track_id': (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.race_track_id)) }], course.spans.filter((s) => s.straight === cond.arg),
'up_slope_random': (course) => coursespans(course.slopes.filter((s) => s.per > 0), SpanType.Random), SpanType.Whenever,
'up_slope_random_later_half': (course) => { ),
straight_random: (course) =>
coursespans(
course.spans.filter((s) => s.straight != null),
SpanType.Random,
),
track_id: (course, cond) => [{ start: 0, end: course.distance, type: must(opcompare(cond, course.race_track_id)) }],
up_slope_random: (course) =>
coursespans(
course.slopes.filter((s) => s.per > 0),
SpanType.Random,
),
up_slope_random_later_half: (course) => {
const p = { start: course.distance / 2, end: course.distance, type: SpanType.Random }; const p = { start: course.distance / 2, end: course.distance, type: SpanType.Random };
return coursespans(course.slopes.filter((s) => s.per > 0), SpanType.Random).map((s) => intersection(s, p)); return coursespans(
} course.slopes.filter((s) => s.per > 0),
SpanType.Random,
).map((s) => intersection(s, p));
},
} as const; } as const;
export function spans(course: racecourse.Course, cond: Condition): Span[] { export function spans(course: racecourse.Course, cond: Condition): Span[] {
+1 -1
View File
@@ -7,7 +7,7 @@ const placementPms = [1000, 400, 250, 150, 100, 90, 80, 70, 60, 50, 40, 30, 25,
* @returns Base fans for the race and placement together * @returns Base fans for the race and placement together
*/ */
export function baseFans(winBase: number, finishOrder: number): number { export function baseFans(winBase: number, finishOrder: number): number {
return Math.round(winBase * placementPms[finishOrder - 1] / 1000); return Math.round((winBase * placementPms[finishOrder - 1]) / 1000);
} }
const margins = [ const margins = [
+3 -3
View File
@@ -196,7 +196,7 @@
> >
&nbsp; &nbsp;
</div> </div>
green with an "Immediate" label. green with an "Immediate" label.
</li> </li>
<li> <li>
Random conditions that are satisfied at a single point within their range, or several points in the case of e.g. <span Random conditions that are satisfied at a single point within their range, or several points in the case of e.g. <span
@@ -206,7 +206,7 @@
particularly common with <span class="font-mono">phase_random==0&nbsp;&amp;&nbsp;accumulatetime&gt;=5</span> skills. These particularly common with <span class="font-mono">phase_random==0&nbsp;&amp;&nbsp;accumulatetime&gt;=5</span> skills. These
conditions are mapped in conditions are mapped in
<div class="inline-block h-4 max-h-4 min-h-4 w-4 max-w-4 min-w-4 border bg-[yellow] align-middle">&nbsp;</div> <div class="inline-block h-4 max-h-4 min-h-4 w-4 max-w-4 min-w-4 border bg-[yellow] align-middle">&nbsp;</div>
yellow with a "Random" label. yellow with a "Random" label.
</li> </li>
<li> <li>
Conditions satisfied by the Uma's other skills triggering. These are mapped in <div Conditions satisfied by the Uma's other skills triggering. These are mapped in <div
@@ -222,7 +222,7 @@
> >
&nbsp; &nbsp;
</div> </div>
red with an "N/A" label. red with an "N/A" label.
</li> </li>
</ul> </ul>
<p> <p>
@@ -50,4 +50,4 @@
]); ]);
</script> </script>
<CourseChart {course} {draw} fontColor="black" /> <CourseChart {course} {draw} />