Skip to content

Commit dfd3f59

Browse files
4sh-devAshDevFr
authored andcommitted
feat: add delta TD/s preview to upgrade hover tooltip
- Extend GeneratorTooltipData with deltaTdPerSecond and milestoneWillCross - Compute delta accounting for milestone threshold crossings - Display "+X TD/s" next-purchase line in GeneratorTooltipContent with neon green glow (yellow when a milestone will be crossed) - Suppress redundant next-milestone row when milestone will trigger on next purchase (the crossing callout already conveys the info) - Add tests for delta values including milestone-crossing boundaries
1 parent 80c0d81 commit dfd3f59

3 files changed

Lines changed: 130 additions & 2 deletions

File tree

src/components/upgrades/GeneratorTooltipContent.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export function GeneratorTooltipContent({
2323
percentOfTotal,
2424
nextMilestoneOwned,
2525
nextMilestoneMultiplier,
26+
deltaTdPerSecond,
27+
milestoneWillCross,
2628
} = computeGeneratorTooltipData(upgrade, owned, allOwned);
2729

2830
const hasMultiplier = milestoneMultiplier > 1 || synergyMultiplier > 1;
@@ -91,7 +93,34 @@ export function GeneratorTooltipContent({
9193
</Box>
9294
)}
9395

94-
{nextMilestoneOwned !== null && (
96+
<Divider />
97+
98+
<Group justify="space-between">
99+
<Text size="xs" c="dimmed" ff="monospace">
100+
Next purchase adds
101+
</Text>
102+
<Text
103+
size="xs"
104+
c={milestoneWillCross ? "yellow" : "green"}
105+
fw={700}
106+
ff="monospace"
107+
style={{
108+
textShadow: milestoneWillCross
109+
? "0 0 6px var(--mantine-color-yellow-5)"
110+
: "0 0 6px #39ff14",
111+
}}
112+
>
113+
+{formatNumber(deltaTdPerSecond)} TD/s
114+
</Text>
115+
</Group>
116+
117+
{milestoneWillCross && (
118+
<Text size="xs" c="yellow" ff="monospace">
119+
🏆 Milestone! All units ×{nextMilestoneMultiplier}
120+
</Text>
121+
)}
122+
123+
{!milestoneWillCross && nextMilestoneOwned !== null && (
95124
<>
96125
<Divider />
97126
<Text size="xs" c="yellow" ff="monospace">

src/components/upgrades/tooltipHelpers.test.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe("computeGeneratorTooltipData", () => {
8787
it("returns correct name and icon", () => {
8888
const data = computeGeneratorTooltipData(neuralNotepad, 0, {});
8989
expect(data.name).toBe("Neural Notepad");
90-
expect(data.icon).toBe("\uD83D\uDCDD");
90+
expect(data.icon).toBe("📝");
9191
});
9292

9393
it("effectiveTdPerUnit equals baseTdPerUnit when no multipliers apply", () => {
@@ -104,4 +104,85 @@ describe("computeGeneratorTooltipData", () => {
104104
data.effectiveTdPerUnit * data.owned,
105105
);
106106
});
107+
108+
// ── Delta TD/s tests ──────────────────────────────────────────────────────
109+
110+
describe("deltaTdPerSecond", () => {
111+
it("equals baseTdPerSecond for the first unit (0 → 1)", () => {
112+
// 0 owned → 1 owned, no milestone yet: delta = 0.2 * 1 * 1 - 0 = 0.2
113+
const data = computeGeneratorTooltipData(neuralNotepad, 0, {});
114+
expect(data.deltaTdPerSecond).toBeCloseTo(0.2);
115+
expect(data.milestoneWillCross).toBe(false);
116+
});
117+
118+
it("equals baseTdPerSecond for a mid-range unit with no milestone (5 → 6)", () => {
119+
// No milestone active (owned < 10): delta = base * 6 * 1 - base * 5 * 1 = base
120+
const allOwned = { "neural-notepad": 5 };
121+
const data = computeGeneratorTooltipData(neuralNotepad, 5, allOwned);
122+
expect(data.deltaTdPerSecond).toBeCloseTo(0.2);
123+
expect(data.milestoneWillCross).toBe(false);
124+
});
125+
126+
it("accounts for milestone crossing when buying the 10th unit (9 → 10)", () => {
127+
// Buying the 10th unit crosses the x1.5 milestone.
128+
// Current: 0.2 * 9 * 1 = 1.8
129+
// Future: 0.2 * 10 * 1.5 = 3.0
130+
// Delta: 3.0 - 1.8 = 1.2
131+
const allOwned = { "neural-notepad": 9 };
132+
const data = computeGeneratorTooltipData(neuralNotepad, 9, allOwned);
133+
expect(data.deltaTdPerSecond).toBeCloseTo(1.2);
134+
expect(data.milestoneWillCross).toBe(true);
135+
});
136+
137+
it("accounts for milestone crossing when buying the 25th unit (24 → 25)", () => {
138+
// Buying the 25th unit crosses the x2 milestone.
139+
// Current: 0.2 * 24 * 1.5 = 7.2
140+
// Future: 0.2 * 25 * 2 = 10.0
141+
// Delta: 10.0 - 7.2 = 2.8
142+
const allOwned = { "neural-notepad": 24 };
143+
const data = computeGeneratorTooltipData(neuralNotepad, 24, allOwned);
144+
expect(data.deltaTdPerSecond).toBeCloseTo(2.8);
145+
expect(data.milestoneWillCross).toBe(true);
146+
});
147+
148+
it("accounts for milestone crossing when buying the 50th unit (49 → 50)", () => {
149+
// Buying the 50th unit crosses the x3 milestone.
150+
// Current: 0.2 * 49 * 2 = 19.6
151+
// Future: 0.2 * 50 * 3 = 30.0
152+
// Delta: 30.0 - 19.6 = 10.4
153+
const allOwned = { "neural-notepad": 49 };
154+
const data = computeGeneratorTooltipData(neuralNotepad, 49, allOwned);
155+
expect(data.deltaTdPerSecond).toBeCloseTo(10.4);
156+
expect(data.milestoneWillCross).toBe(true);
157+
});
158+
159+
it("applies normal delta after milestone (owned=10, 10 → 11)", () => {
160+
// x1.5 milestone active, no crossing.
161+
// Current: 0.2 * 10 * 1.5 = 3.0
162+
// Future: 0.2 * 11 * 1.5 = 3.3
163+
// Delta: 0.3
164+
const allOwned = { "neural-notepad": 10 };
165+
const data = computeGeneratorTooltipData(neuralNotepad, 10, allOwned);
166+
expect(data.deltaTdPerSecond).toBeCloseTo(0.3);
167+
expect(data.milestoneWillCross).toBe(false);
168+
});
169+
170+
it("applies normal delta at max milestone (owned=100, 100 → 101)", () => {
171+
// x6 milestone active, no further milestones.
172+
// Current: 0.2 * 100 * 6 = 120.0
173+
// Future: 0.2 * 101 * 6 = 121.2
174+
// Delta: 1.2
175+
const allOwned = { "neural-notepad": 100 };
176+
const data = computeGeneratorTooltipData(neuralNotepad, 100, allOwned);
177+
expect(data.deltaTdPerSecond).toBeCloseTo(1.2);
178+
expect(data.milestoneWillCross).toBe(false);
179+
});
180+
181+
it("milestoneWillCross is false well before a threshold", () => {
182+
const data = computeGeneratorTooltipData(neuralNotepad, 7, {
183+
"neural-notepad": 7,
184+
});
185+
expect(data.milestoneWillCross).toBe(false);
186+
});
187+
});
107188
});

src/components/upgrades/tooltipHelpers.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export interface GeneratorTooltipData {
2121
nextMilestoneOwned: number | null;
2222
nextMilestoneMultiplier: number | null;
2323
nextMilestoneLabel: string | null;
24+
/** TD/s gained by purchasing one more unit (accounts for milestone crossing). */
25+
deltaTdPerSecond: number;
26+
/** True when buying the next unit crosses a milestone threshold. */
27+
milestoneWillCross: boolean;
2428
}
2529

2630
/**
@@ -51,6 +55,18 @@ export function computeGeneratorTooltipData(
5155

5256
const nextThreshold = MILESTONE_THRESHOLDS[milestoneLevel] ?? null;
5357

58+
// Delta: TD/s gained by purchasing one more unit.
59+
// Milestone crossing is handled correctly: if owned+1 hits a threshold,
60+
// ALL existing units benefit from the new multiplier too.
61+
const newOwned = owned + 1;
62+
const newAllOwned = { ...allOwned, [upgrade.id]: newOwned };
63+
const newMilestoneMultiplier = getMilestoneMultiplier(newOwned);
64+
const newSynergyMultiplier = getSynergyMultiplier(upgrade.id, newAllOwned);
65+
const futureTdForGenerator =
66+
upgrade.baseTdPerSecond * newOwned * newMilestoneMultiplier * newSynergyMultiplier;
67+
const deltaTdPerSecond = futureTdForGenerator - totalTdForGenerator;
68+
const milestoneWillCross = getMilestoneLevel(newOwned) > milestoneLevel;
69+
5470
return {
5571
name: upgrade.name,
5672
icon: upgrade.icon,
@@ -64,5 +80,7 @@ export function computeGeneratorTooltipData(
6480
nextMilestoneOwned: nextThreshold?.owned ?? null,
6581
nextMilestoneMultiplier: nextThreshold?.multiplier ?? null,
6682
nextMilestoneLabel: nextThreshold?.label ?? null,
83+
deltaTdPerSecond,
84+
milestoneWillCross,
6785
};
6886
}

0 commit comments

Comments
 (0)