zenno: format & lint

This commit is contained in:
2026-07-06 09:41:11 -04:00
parent 06e5a06680
commit 55175c9c40
7 changed files with 413 additions and 341 deletions
+3 -1
View File
@@ -73,7 +73,9 @@
const series = $derived([y].flat(1).filter((s) => s != null));
const areas = $derived([yArea].flat(1).filter((s) => s != null));
const vals = $derived(series.flatMap(({ y, label }) => xVal.map((x) => ({ x, y: y(adj(x), x), label }))));
const areaVals = $derived(areas.flatMap(({ y1, y2, label }) => xVal.map((x) => ({ x, y1: y1(adj(x), x), y2: y2(adj(x), x), label }))));
const areaVals = $derived(
areas.flatMap(({ y1, y2, label }) => xVal.map((x) => ({ x, y1: y1(adj(x), x), y2: y2(adj(x), x), label }))),
);
const yRange: [number, number] = $derived.by(() => {
if (range != null) {
if (range.length === 2) {
+53 -22
View File
@@ -34,13 +34,7 @@ export enum Mood {
/**
* Moods as a descending list for easy iteration.
*/
export const MOODS = [
Mood.Great,
Mood.Good,
Mood.Normal,
Mood.Bad,
Mood.Awful,
] as const;
export const MOODS = [Mood.Great, Mood.Good, Mood.Normal, Mood.Bad, Mood.Awful] as const;
const MOOD_COEF = {
[Mood.Awful]: 0.96,
@@ -61,7 +55,7 @@ function statClamp(x: number): number {
* @returns Base stat value
*/
export function baseStat(mood: Mood, rawStat: number): number {
const r = (math.min(rawStat, 1200) + math.max(rawStat - 1200, 0)*0.5) * MOOD_COEF[mood];
const r = (math.min(rawStat, 1200) + math.max(rawStat - 1200, 0) * 0.5) * MOOD_COEF[mood];
return statClamp(r);
}
@@ -72,7 +66,7 @@ export enum Distance {
Sprint,
Mile,
Medium,
Long
Long,
}
/**
@@ -104,7 +98,7 @@ export function phases(raceLen: number) {
[Phase.EarlyRace]: sixth,
[Phase.MidRace]: 4 * sixth,
[Phase.LateRace]: raceLen,
}
};
}
/**
@@ -176,7 +170,14 @@ const strategyProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.75, 0.85, 1.0, 1.1] as con
* @param conditions Race ground conditions
* @param thresholdMod Achieved stat threshold modifier for the race track
*/
export function adjustedStat(speed: Stat.Speed, baseSpeed: number, career: boolean, surface: Surface, conditions: GroundConditions, thresholdMod: number): number;
export function adjustedStat(
speed: Stat.Speed,
baseSpeed: number,
career: boolean,
surface: Surface,
conditions: GroundConditions,
thresholdMod: number,
): number;
/**
* Compute adjusted stamina for a race.
* @param stam
@@ -192,7 +193,13 @@ export function adjustedStat(stam: Stat.Stamina, baseStam: number, career: boole
* @param surface Race surface type
* @param conditions Race ground conditions
*/
export function adjustedStat(power: Stat.Power, basePower: number, career: boolean, surface: Surface, conditions: GroundConditions): number;
export function adjustedStat(
power: Stat.Power,
basePower: number,
career: boolean,
surface: Surface,
conditions: GroundConditions,
): number;
/**
* Compute adjusted guts for a race.
* @param guts
@@ -208,7 +215,14 @@ export function adjustedStat(guts: Stat.Guts, baseGuts: number, career: boolean)
* @param styleApt Aptitude for the horse's current running style
*/
export function adjustedStat(wit: Stat.Wit, baseWit: number, career: boolean, styleApt: AptitudeLevel): number;
export function adjustedStat(stat: Stat, baseStat: number, career: boolean, surfaceOrStyle?: Surface | AptitudeLevel, conditions?: GroundConditions, thresholdMod?: number): number {
export function adjustedStat(
stat: Stat,
baseStat: number,
career: boolean,
surfaceOrStyle?: Surface | AptitudeLevel,
conditions?: GroundConditions,
thresholdMod?: number,
): number {
const careerMod = career ? 400 : 0;
switch (stat) {
case Stat.Speed:
@@ -229,13 +243,13 @@ export function thresholdMod(stat1: number, stat2?: number): number {
return 0.5 * (thresholdMod(stat1) + thresholdMod(stat2));
}
if (stat1 > 900) {
return 1.20;
return 1.2;
}
if (stat1 > 600) {
return 1.15;
}
if (stat1 > 300) {
return 1.10;
return 1.1;
}
return 1.05;
}
@@ -337,7 +351,7 @@ const groundHPRateMod = {
[GroundConditions.Good]: 1,
[GroundConditions.Soft]: 1.01,
[GroundConditions.Heavy]: 1.02,
}
},
} as const;
/**
@@ -352,14 +366,24 @@ const groundHPRateMod = {
* @param pdm Whether pace-down mode is active (default false)
* @param rushed Whether the horse is currently rushed (default false)
*/
export function hpPerSecond(raceLen: number, surface: Surface, conditions: GroundConditions, gutsStat: number, phase: Phase, currentSpeed: number, dam?: boolean, pdm?: boolean, rushed?: boolean): number {
export function hpPerSecond(
raceLen: number,
surface: Surface,
conditions: GroundConditions,
gutsStat: number,
phase: Phase,
currentSpeed: number,
dam?: boolean,
pdm?: boolean,
rushed?: boolean,
): number {
const rushedMod = rushed ? 1.6 : 1;
const pdmMod = pdm ? 0.6 : 1;
const damMod = dam ? 0.4 : 1;
const groundMod = groundHPRateMod[surface][conditions];
const gutsMod = phase === Phase.LateRace ? 1 + 200/(math.sqrt(600 * gutsStat) as number) : 1;
const gutsMod = phase === Phase.LateRace ? 1 + 200 / (math.sqrt(600 * gutsStat) as number) : 1;
const sp = currentSpeed - baseSpeed(raceLen) + 12;
return 20/144 * sp * sp * rushedMod * pdmMod * damMod * groundMod * gutsMod;
return (20 / 144) * sp * sp * rushedMod * pdmMod * damMod * groundMod * gutsMod;
}
/**
@@ -695,13 +719,20 @@ const conserveStratDistCoef = {
},
} as const;
export function conservePowerAccel(distanceType: Distance, style: RunningStyle, rawPower: number, powerSkillBonus?: number, spotStruggled?: boolean, rushed?: boolean): number {
export function conservePowerAccel(
distanceType: Distance,
style: RunningStyle,
rawPower: number,
powerSkillBonus?: number,
spotStruggled?: boolean,
rushed?: boolean,
): number {
const power = rawPower + (powerSkillBonus ?? 0);
if (power <= 1200) {
return 0;
}
const sdc = conserveStratDistCoef[style][distanceType];
const activity = rushed ? 0.8 : (spotStruggled ? 0.98 : 1);
const activity = rushed ? 0.8 : spotStruggled ? 0.98 : 1;
return Math.sqrt((power - 1200) * 130) * 0.001 * sdc * activity;
}
@@ -781,7 +812,7 @@ export function uphillMod(powerStat: number, slopePer: number): number {
* @returns Speed modifier for downhill accel mode
*/
export function downhillAccelMod(slopePer: number): number {
return 0.3 - slopePer/10;
return 0.3 - slopePer / 10;
}
/**
+15 -8
View File
@@ -30,17 +30,24 @@ export interface Travel {
* @param decel Negative acceleration value for cases where target speed is below start speed
* @returns Travel segment after reaching the first of the given limits
*/
export function travelSegment(start: Instant, targetSpeed: number, totalAccel: number, maxDur?: number, maxDist?: number, decel?: number): Travel {
const a = start.v < targetSpeed ? totalAccel : decel ?? -1;
export function travelSegment(
start: Instant,
targetSpeed: number,
totalAccel: number,
maxDur?: number,
maxDist?: number,
decel?: number,
): Travel {
const a = start.v < targetSpeed ? totalAccel : (decel ?? -1);
const dvt = (targetSpeed - start.v) / a;
const dxt = maxDist != null ? (-start.v + Math.sqrt(start.v*start.v + 4*a*maxDist)) / (2*a) : dvt;
const dxt = maxDist != null ? (-start.v + Math.sqrt(start.v * start.v + 4 * a * maxDist)) / (2 * a) : dvt;
const time = maxDur != null ? Math.min(dvt, dxt, maxDur) : Math.min(dvt, dxt);
// To address numerical imprecision, if accel time comes from velocity or distance,
// set the reached values directly.
const v = time === dvt ? targetSpeed : start.v + time * a;
const dist = maxDist != null && time === dxt ? maxDist : (start.v + 0.5 * a * time) * time;
const end = { t: start.t + time, x: start.x + dist, v };
return {start, end, a};
return { start, end, a };
}
/**
@@ -67,7 +74,7 @@ export interface Skill {
*/
export function up(start: Instant, targetSpeed: number, baseAccel: number, skills: Skill[]) {
const segments: Travel[] = [];
const sk = skills.map((s) => ({...s})).filter((s) => s.dur > 0);
const sk = skills.map((s) => ({ ...s })).filter((s) => s.dur > 0);
while (start.v < targetSpeed) {
const active = sk.filter((s) => s.dur > 0);
if (active.length === 0) {
@@ -78,13 +85,13 @@ export function up(start: Instant, targetSpeed: number, baseAccel: number, skill
const time = Math.min(...active.map((s) => s.dur));
const seg = travelSegment(start, targetSpeed, baseAccel + boost, time);
segments.push(seg);
start = seg.end
start = seg.end;
for (const s of active) {
s.dur -= seg.end.t - seg.start.t;
if (s.dur < 1e-5) {
s.dur = 0
s.dur = 0;
}
}
}
return {segments, skills: sk}
return { segments, skills: sk };
}
+1 -1
View File
@@ -18,7 +18,7 @@ declare const __stage: unique symbol;
export type Stage = 'raw' | 'base' | 'adjusted' | 'final' | 'skills';
export type StatValue<T extends Stage> = number & {[__stage]: T};
export type StatValue<T extends Stage> = number & { [__stage]: T };
export interface Stats<T extends Stage> {
speed: StatValue<T>;
+10 -5
View File
@@ -58,9 +58,11 @@
]);
const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }];
const conservePower: Array<ComputedSeries | null> = $derived([
{ label: 'Conserved Power', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, false)},
{ label: 'Rushed', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, true)},
styleIsFront ? { label: 'Spot Struggled', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, true, false)} : null,
{ label: 'Conserved Power', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, false) },
{ label: 'Rushed', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, true) },
styleIsFront
? { label: 'Spot Struggled', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, true, false) }
: null,
]);
const gutsSpurt: Array<ComputedSeries | null> = $derived([
{ label: `Speed 600`, y: (x) => race.spurtSpeed(600, x, style, distanceApt, raceLen) },
@@ -283,8 +285,11 @@
<Sec h={3} id="conserve-power">Conserve Power Acceleration</Sec>
<p>Bonus acceleration at spurt start from the Conserve Power mechanic.</p>
<p>This mechanic does not appear to be well understood, in particular how long the acceleration bonus lasts and how the rushed and spot struggle multipliers apply.</p>
{@render statChart(race.Stat.Power, conservePower, 'Acceleration (m/s²)', [0, 0.35], {len: true, style: true})}
<p>
This mechanic does not appear to be well understood, in particular how long the acceleration bonus lasts and how the rushed
and spot struggle multipliers apply.
</p>
{@render statChart(race.Stat.Power, conservePower, 'Acceleration (m/s²)', [0, 0.35], { len: true, style: true })}
<Sec h={2} id="guts">Guts</Sec>
+77 -42
View File
@@ -20,15 +20,27 @@
let slope = $state(0);
let rawSkills: accel.Skill[] = $state([]);
let skillToAdd: accel.Skill = $state({accel: 0.2, dur: 1.2});
let skillToAdd: accel.Skill = $state({ accel: 0.2, dur: 1.2 });
const quick = [
{name: "GW+TTL", skills: [{accel: 0.2, dur: 3}, {accel: 0.4, dur: 1.2}]},
{name: "GW+EL", skills: [{accel: 0.2, dur: 3}, {accel: 0.2, dur: 1.2}]},
{name: "Clear", skills: []},
{
name: 'GW+TTL',
skills: [
{ accel: 0.2, dur: 3 },
{ accel: 0.4, dur: 1.2 },
],
},
{
name: 'GW+EL',
skills: [
{ accel: 0.2, dur: 3 },
{ accel: 0.2, dur: 1.2 },
],
},
{ name: 'Clear', skills: [] },
];
function addSkill() {
rawSkills.push(skillToAdd);
skillToAdd = {accel: 0.2, dur: 1.2};
skillToAdd = { accel: 0.2, dur: 1.2 };
}
function removeSkill(i: number) {
rawSkills.splice(i, 1);
@@ -49,8 +61,12 @@
const adjWit = $derived(race.adjustedStat(race.Stat.Wit, baseWit, isCareer, styleApt));
const wit = $derived(race.finalStat(adjWit, witBonus));
const dashAccel = $derived(race.acceleration(power, style, surfApt, race.Phase.EarlyRace, race.AptitudeLevel.A, slope > 0, true));
const postAccel = $derived(race.acceleration(power, style, surfApt, race.Phase.EarlyRace, race.AptitudeLevel.A, slope > 0, false));
const dashAccel = $derived(
race.acceleration(power, style, surfApt, race.Phase.EarlyRace, race.AptitudeLevel.A, slope > 0, true),
);
const postAccel = $derived(
race.acceleration(power, style, surfApt, race.Phase.EarlyRace, race.AptitudeLevel.A, slope > 0, false),
);
const minSpeed = $derived(race.minSpeed(raceLen, guts));
const baseSpeed = $derived(race.baseSpeed(raceLen));
const dashTarget = $derived(baseSpeed * 0.85);
@@ -63,15 +79,14 @@
return [0, race.downhillAccelMod(slope)];
}
return [0, 0];
})
});
const earlyTarget = $derived(race.sectionSpeed(raceLen, baseWit, wit, style, race.Phase.EarlyRace));
const speeds: [number, number] = $derived([earlyTarget[0] + hillMod[0], earlyTarget[1] + hillMod[1]]);
const avgEarlyTarget = $derived((earlyTarget[0] + earlyTarget[1]) * 0.5);
const startInstant: accel.Instant = {t: 0, x: 0, v: 3};
const startInstant: accel.Instant = { t: 0, x: 0, v: 3 };
function doSeggsToIt(targetSpeed: number, skills: accel.Skill[]) {
const startDash = accel.up(startInstant, dashTarget, dashAccel, skills);
const postStart = {...last(startDash.segments).end, v: minSpeed};
const postStart = { ...last(startDash.segments).end, v: minSpeed };
const post = accel.up(postStart, targetSpeed, postAccel, startDash.skills);
const r = [...startDash.segments, ...post.segments];
return r;
@@ -79,21 +94,15 @@
function last<T>(l: T[]): T {
return l[l.length - 1];
}
const skills = $derived(rawSkills.map((s) => ({...s, dur: race.skillDuration(s.dur, raceLen)})));
const skills = $derived(rawSkills.map((s) => ({ ...s, dur: race.skillDuration(s.dur, raceLen) })));
const seggs = $derived([doSeggsToIt(speeds[0], skills), doSeggsToIt(speeds[1], skills)]);
const unskilledSeggs = $derived([doSeggsToIt(speeds[0], []), doSeggsToIt(speeds[1], [])]);
const distance = $derived([
last(seggs[0]).end.x,
last(seggs[1]).end.x,
]);
const time = $derived([
last(seggs[0]).end.t,
last(seggs[1]).end.t,
]);
const distance = $derived([last(seggs[0]).end.x, last(seggs[1]).end.x]);
const time = $derived([last(seggs[0]).end.t, last(seggs[1]).end.t]);
const midTime = $derived([
time[0] + (phases[race.Phase.EarlyRace] - distance[0])/speeds[1],
time[1] + (phases[race.Phase.EarlyRace] - distance[1])/speeds[0],
time[0] + (phases[race.Phase.EarlyRace] - distance[0]) / speeds[1],
time[1] + (phases[race.Phase.EarlyRace] - distance[1]) / speeds[0],
]);
function gainLengths(fast: accel.Instant, slow: accel.Instant): number {
const m = fast.v * (slow.t - fast.t) + fast.x - slow.x;
@@ -106,7 +115,7 @@
</script>
<h1 class="text-4xl">Gate Calculator</h1>
<form class="mx-auto my-4 p-4 grid gap-4 grid-cols-1 max-w-4xl rounded-md shadow-sm md:grid-cols-5">
<form class="mx-auto my-4 grid max-w-4xl grid-cols-1 gap-4 rounded-md p-4 shadow-sm md:grid-cols-5">
<div class="grid rounded-md text-center shadow-md ring md:col-span-5 md:grid-cols-5">
<div class="m-4">
<label for="powerStat">Power</label>
@@ -123,7 +132,7 @@
<div class="m-4">
<label for="style">Style</label>
<select id="style" required bind:value={style} class="w-full">
{#each race.RUNNING_STYLES as [name, style]}
{#each race.RUNNING_STYLES as [name, style] (style)}
<option value={style}>{name}</option>
{/each}
</select>
@@ -165,50 +174,70 @@
</select>
</div>
</div>
<div class="mb-auto grid grid-cols-6 max-w-4xl rounded-md text-center shadow-md ring md:col-span-3">
<input class="m-4 w-full col-span-4 justify-self-center-safe" type="range" id="raceLen" min="1000" max="3600" step="100" bind:value={raceLen} />
<span class="mb-4 my-auto pl-2 col-span-2 text-sm">{raceLen}m</span>
<select id="surface" bind:value={surface} class="m-4 col-span-2">
<div class="mb-auto grid max-w-4xl grid-cols-6 rounded-md text-center shadow-md ring md:col-span-3">
<input
class="col-span-4 m-4 w-full justify-self-center-safe"
type="range"
id="raceLen"
min="1000"
max="3600"
step="100"
bind:value={raceLen}
/>
<span class="col-span-2 my-auto mb-4 pl-2 text-sm">{raceLen}m</span>
<select id="surface" bind:value={surface} class="col-span-2 m-4">
<option value={race.Surface.Turf}>Turf</option>
<option value={race.Surface.Dirt}>Dirt</option>
</select>
<select id="conditions" bind:value={conditions} class="m-4 col-span-2">
<select id="conditions" bind:value={conditions} class="col-span-2 m-4">
{#each race.GROUND_CONDITONS as cond (cond)}
<option value={cond}>{race.GroundConditions[cond]}</option>
{/each}
</select>
<select id="slope" bind:value={slope} class="m-4 col-span-2">
<select id="slope" bind:value={slope} class="col-span-2 m-4">
{#each [2, 1.5, 1, 0, -1, -1.5, -2] as per (per)}
<option value={per}>{per > 0 ? `Uphill +${per}` : (per < 0 ? `Downhill ${per}` : 'Flat')}</option>
<option value={per}>{per > 0 ? `Uphill +${per}` : per < 0 ? `Downhill ${per}` : 'Flat'}</option>
{/each}
</select>
</div>
<div class="mb-auto w-full flex flex-col max-w-4xl rounded-md text-center shadow-md ring md:col-span-2">
<div class="mb-auto flex w-full max-w-4xl flex-col rounded-md text-center shadow-md ring md:col-span-2">
<div class="mx-4 mt-2 flex flex-row gap-2">
<span class="w-24 flex-1">Duration</span>
<span class="w-24 flex-1">Accel</span>
<span class="flex-none w-8"><!-- filler to align other columns --></span>
<span class="w-8 flex-none"><!-- filler to align other columns --></span>
</div>
{#each rawSkills as skill, i (i)}
<div class="mx-4 mt-2 flex flex-row gap-2">
<span class="my-auto w-24 flex-1">{skill.dur}s</span>
<span class="my-auto w-24 flex-1">{skill.accel}</span>
<button type="button" class="my-auto flex-none w-8 h-8 rounded-full text-2xl hover:cursor-pointer bg-mist-300 dark:bg-mist-900" onclick={() => removeSkill(i)}>&times;</button>
<button
type="button"
class="my-auto h-8 w-8 flex-none rounded-full bg-mist-300 text-2xl hover:cursor-pointer dark:bg-mist-900"
onclick={() => removeSkill(i)}>&times;</button
>
</div>
{/each}
<div class="mx-4 mt-2 flex flex-row gap-2">
<input type="number" class="my-auto w-24 flex-1" min="0.9" step="0.3" bind:value={skillToAdd.dur} />
<input type="number" class="my-auto w-24 flex-1" min="0" step="0.05" bind:value={skillToAdd.accel} />
<button type="button" class="my-auto flex-none w-8 h-8 rounded-full text-2xl hover:cursor-pointer bg-mist-300 dark:bg-mist-900" onclick={addSkill}>+</button>
<button
type="button"
class="my-auto h-8 w-8 flex-none rounded-full bg-mist-300 text-2xl hover:cursor-pointer dark:bg-mist-900"
onclick={addSkill}>+</button
>
</div>
<div class="flex flex-row mt-1">
<div class="mt-1 flex flex-row">
<span class="my-auto h-0 flex-1 border"></span>
<span class="mx-2 flex-none">Quick Setup</span>
<span class="my-auto h-0 flex-1 border"></span>
</div>
<div class="flex flex-row m-2 gap-2 place-content-center-safe">
<div class="m-2 flex flex-row place-content-center-safe gap-2">
{#each quick as q (q.name)}
<button type="button" class="p-2 rounded-md hover:cursor-pointer bg-mist-300 dark:bg-mist-900" onclick={() => setSkillList(q.skills)}>{q.name}</button>
<button
type="button"
class="rounded-md bg-mist-300 p-2 hover:cursor-pointer dark:bg-mist-900"
onclick={() => setSkillList(q.skills)}>{q.name}</button
>
{/each}
</div>
</div>
@@ -250,7 +279,7 @@
</tr>
</thead>
<tbody>
{#each s as {start, end, a} (start.t)}
{#each s as { start, end, a } (start.t)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-4 text-center">{end.t.toFixed(3)}</td>
<td class="px-4 text-center">{a.toFixed(3)}</td>
@@ -261,14 +290,20 @@
</tbody>
</table>
{/snippet}
<div class="mx-auto mt-8 max-w-4xl grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="mx-auto mt-8 grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-2">
{@render seggsTable(slope < 0 ? 'Fastest Speed (Downhill)' : 'Fastest Speed', seggs[1])}
{@render seggsTable('Slowest Speed', seggs[0])}
</div>
<div class="mx-auto mt-12 w-full max-w-4xl border-t pt-4 md:mt-8">
<p>This calculator integrates acceleration analytically for efficiency, but the game uses a finite step approximation. The difference has some implications:</p>
<p>
This calculator integrates acceleration analytically for efficiency, but the game uses a finite step approximation. The
difference has some implications:
</p>
<ul class="ml-4 list-disc">
<li>Late starts are dramatically more punishing in the game, as acceleration is not applied for the first frame.</li>
<li>Start dash always completes a single frame at the dash target speed. This calculator finds the exact moment start dash target speed would be reached and continues integrating from there, leading to a slightly higher speed.</li>
<li>
Start dash always completes a single frame at the dash target speed. This calculator finds the exact moment start dash
target speed would be reached and continues integrating from there, leading to a slightly higher speed.
</li>
</ul>
</div>
+1 -9
View File
@@ -164,15 +164,7 @@
{/each}
</div>
<div class="mx-auto grid h-96 grid-cols-1 py-4 md:grid-cols-2">
<StatChart
class="flex-1"
stat={Stat.Guts}
y={ssY}
yLabel="Lengths Gained"
range={[0, 2]}
xRule={gutsStat}
yRule={ssYRule}
/>
<StatChart class="flex-1" stat={Stat.Guts} y={ssY} yLabel="Lengths Gained" range={[0, 2]} xRule={gutsStat} yRule={ssYRule} />
<StatChart
class="flex-1"
stat={Stat.Power}