fix: use actual meditationMultiplier and baseRegen in ElementStatsSection; add cost breakdown per spec §11
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-06-08T11:14:13.243Z
|
Generated: 2026-06-08T11:29:45.994Z
|
||||||
Found: 1 circular chain(s) — these MUST be fixed before modifying involved files.
|
Found: 1 circular chain(s) — these MUST be fixed before modifying involved files.
|
||||||
|
|
||||||
1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts
|
1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-06-08T11:14:11.296Z",
|
"generated": "2026-06-08T11:29:44.022Z",
|
||||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export function StatsTab() {
|
|||||||
/>
|
/>
|
||||||
<ElementStatsSection
|
<ElementStatsSection
|
||||||
elemMax={elemMax}
|
elemMax={elemMax}
|
||||||
|
meditationMultiplier={manaStats.meditationMultiplier}
|
||||||
|
baseRegen={manaStats.baseRegen}
|
||||||
/>
|
/>
|
||||||
<LoopStatsSection />
|
<LoopStatsSection />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ import type { ElementState } from '@/lib/game/types';
|
|||||||
|
|
||||||
interface ElementStatsSectionProps {
|
interface ElementStatsSectionProps {
|
||||||
elemMax: number;
|
elemMax: number;
|
||||||
|
meditationMultiplier: number;
|
||||||
|
baseRegen: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ElementStatsSection({ elemMax }: ElementStatsSectionProps) {
|
export function ElementStatsSection({ elemMax, meditationMultiplier, baseRegen }: ElementStatsSectionProps) {
|
||||||
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
||||||
const signedPacts = usePrestigeStore((s) => s.signedPacts);
|
const signedPacts = usePrestigeStore((s) => s.signedPacts);
|
||||||
const elements = useManaStore((s) => s.elements);
|
const elements = useManaStore((s) => s.elements);
|
||||||
@@ -48,9 +50,9 @@ export function ElementStatsSection({ elemMax }: ElementStatsSectionProps) {
|
|||||||
signedPacts,
|
signedPacts,
|
||||||
pactElementMap,
|
pactElementMap,
|
||||||
invokerLevel,
|
invokerLevel,
|
||||||
meditationMultiplier: 1,
|
meditationMultiplier,
|
||||||
grossRegen,
|
grossRegen,
|
||||||
rawGrossRegen: 2,
|
rawGrossRegen: baseRegen,
|
||||||
});
|
});
|
||||||
}, [disciplines, attunements, signedPacts]);
|
}, [disciplines, attunements, signedPacts]);
|
||||||
|
|
||||||
@@ -123,7 +125,7 @@ export function ElementStatsSection({ elemMax }: ElementStatsSectionProps) {
|
|||||||
<div className="text-xs font-medium mb-2" style={{ color: 'var(--text-muted)' }}>Conversion Breakdown:</div>
|
<div className="text-xs font-medium mb-2" style={{ color: 'var(--text-muted)' }}>Conversion Breakdown:</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{activeConversions.map((entry) => (
|
{activeConversions.map((entry) => (
|
||||||
<ConversionRow key={entry.element} entry={entry} />
|
<ConversionRow key={entry.element} entry={entry} elementDrain={conversionData.elementDrain} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -134,8 +136,31 @@ export function ElementStatsSection({ elemMax }: ElementStatsSectionProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConversionRow({ entry }: { entry: { element: string; distance: number; disciplineRate: number; attunementBase: number; pactBase: number; baseRate: number; attunementMult: number; pactMult: number; meditationMult: number; finalRate: number; paused: boolean; pauseReason: string | null } }) {
|
function ConversionRow({ entry, elementDrain }: {
|
||||||
|
entry: {
|
||||||
|
element: string;
|
||||||
|
distance: number;
|
||||||
|
disciplineRate: number;
|
||||||
|
attunementBase: number;
|
||||||
|
pactBase: number;
|
||||||
|
baseRate: number;
|
||||||
|
attunementMult: number;
|
||||||
|
pactMult: number;
|
||||||
|
meditationMult: number;
|
||||||
|
finalRate: number;
|
||||||
|
paused: boolean;
|
||||||
|
pauseReason: string | null;
|
||||||
|
rawCost: number;
|
||||||
|
componentCosts: Record<string, number>;
|
||||||
|
};
|
||||||
|
elementDrain: Record<string, number>;
|
||||||
|
}) {
|
||||||
const def = ELEMENTS[entry.element];
|
const def = ELEMENTS[entry.element];
|
||||||
|
const effectiveRate = entry.paused ? 0 : entry.finalRate;
|
||||||
|
const rawDrain = effectiveRate * entry.rawCost;
|
||||||
|
const isComponentDrained = Object.keys(entry.componentCosts).length > 0 &&
|
||||||
|
Object.keys(elementDrain).length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-2 rounded text-xs" style={{ border: `1px solid ${def?.color}20`, background: 'var(--bg-sunken)/30' }}>
|
<div className="p-2 rounded text-xs" style={{ border: `1px solid ${def?.color}20`, background: 'var(--bg-sunken)/30' }}>
|
||||||
<div className="flex items-center gap-1 mb-1" style={{ color: def?.color }}>
|
<div className="flex items-center gap-1 mb-1" style={{ color: def?.color }}>
|
||||||
@@ -144,6 +169,7 @@ function ConversionRow({ entry }: { entry: { element: string; distance: number;
|
|||||||
<span style={{ color: 'var(--text-muted)' }}>(d={entry.distance})</span>
|
<span style={{ color: 'var(--text-muted)' }}>(d={entry.distance})</span>
|
||||||
{entry.paused && <span style={{ color: 'var(--color-error)' }}>⏸️ PAUSED</span>}
|
{entry.paused && <span style={{ color: 'var(--color-error)' }}>⏸️ PAUSED</span>}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Rate breakdown */}
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-0.5" style={{ color: 'var(--text-muted)' }}>
|
<div className="grid grid-cols-2 gap-x-4 gap-y-0.5" style={{ color: 'var(--text-muted)' }}>
|
||||||
<div>Discipline: <span style={{ color: 'var(--color-success)' }}>+{fmtDec(entry.disciplineRate, 2)}/hr</span></div>
|
<div>Discipline: <span style={{ color: 'var(--color-success)' }}>+{fmtDec(entry.disciplineRate, 2)}/hr</span></div>
|
||||||
<div>Attunement: <span style={{ color: 'var(--color-success)' }}>+{fmtDec(entry.attunementBase, 2)}/hr</span></div>
|
<div>Attunement: <span style={{ color: 'var(--color-success)' }}>+{fmtDec(entry.attunementBase, 2)}/hr</span></div>
|
||||||
@@ -154,6 +180,32 @@ function ConversionRow({ entry }: { entry: { element: string; distance: number;
|
|||||||
<div>Med mult: <span>×{fmtDec(entry.meditationMult, 2)}</span></div>
|
<div>Med mult: <span>×{fmtDec(entry.meditationMult, 2)}</span></div>
|
||||||
<div>Final: <span style={{ color: entry.paused ? 'var(--color-error)' : 'var(--color-success)' }}>{entry.paused ? '0.00' : fmtDec(entry.finalRate, 2)}/hr</span></div>
|
<div>Final: <span style={{ color: entry.paused ? 'var(--color-error)' : 'var(--color-success)' }}>{entry.paused ? '0.00' : fmtDec(entry.finalRate, 2)}/hr</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Cost breakdown (per spec §11) */}
|
||||||
|
{!entry.paused && effectiveRate > 0 && (
|
||||||
|
<div className="mt-1.5 pt-1.5 border-t border-[var(--border-subtle)]">
|
||||||
|
<div className="mb-0.5" style={{ color: 'var(--text-muted)' }}>Costs (deducted from regen):</div>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4 gap-y-0.5" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
<div>Raw: <span style={{ color: 'var(--color-warning)' }}>-{fmtDec(rawDrain, 2)} raw/hr</span> <span style={{ color: 'var(--text-muted)' }}>({entry.rawCost} × {fmtDec(effectiveRate, 2)})</span></div>
|
||||||
|
{Object.entries(entry.componentCosts).map(([comp, cost]) => {
|
||||||
|
const compDrain = effectiveRate * cost;
|
||||||
|
const compDef = ELEMENTS[comp];
|
||||||
|
return (
|
||||||
|
<div key={comp}>
|
||||||
|
<span style={{ color: compDef?.color }}>{compDef?.sym} {comp}:</span>{' '}
|
||||||
|
<span style={{ color: 'var(--color-warning)' }}>-{fmtDec(compDrain, 2)}/hr</span>{' '}
|
||||||
|
<span style={{ color: 'var(--text-muted)' }}>({cost} × {fmtDec(effectiveRate, 2)})</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{/* Downstream drain (this element consumed by higher conversions) */}
|
||||||
|
{isComponentDrained && elementDrain[entry.element] > 0 && (
|
||||||
|
<div className="mt-1" style={{ color: 'var(--color-warning)' }}>
|
||||||
|
↓ Drained by downstream: -{fmtDec(elementDrain[entry.element], 2)} {def?.name}/hr
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{entry.paused && entry.pauseReason && (
|
{entry.paused && entry.pauseReason && (
|
||||||
<div className="mt-1" style={{ color: 'var(--color-error)' }}>⚠️ {entry.pauseReason}</div>
|
<div className="mt-1" style={{ color: 'var(--color-error)' }}>⚠️ {entry.pauseReason}</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user