Improve error handling

This commit is contained in:
thepaperpilot 2023-05-16 23:38:31 -05:00
parent 500e412fdb
commit ab3b180db8
12 changed files with 426 additions and 156 deletions

View file

@ -1,4 +1,6 @@
<template> <template>
<div v-if="appErrors.length > 0" class="error-container" :style="theme"><Error :errors="appErrors" /></div>
<template v-else>
<div id="modal-root" :style="theme" /> <div id="modal-root" :style="theme" />
<div class="app" :style="theme" :class="{ useHeader }"> <div class="app" :style="theme" :class="{ useHeader }">
<Nav v-if="useHeader" /> <Nav v-if="useHeader" />
@ -8,11 +10,16 @@
<NaNScreen /> <NaNScreen />
<component :is="gameComponent" /> <component :is="gameComponent" />
</div> </div>
</template>
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import "@fontsource/roboto-mono";
import Error from "components/Error.vue";
import { jsx } from "features/feature"; import { jsx } from "features/feature";
import state from "game/state";
import { coerceComponent, render } from "util/vue"; import { coerceComponent, render } from "util/vue";
import { CSSProperties, watch } from "vue";
import { computed, toRef, unref } from "vue"; import { computed, toRef, unref } from "vue";
import Game from "./components/Game.vue"; import Game from "./components/Game.vue";
import GameOverScreen from "./components/GameOverScreen.vue"; import GameOverScreen from "./components/GameOverScreen.vue";
@ -23,12 +30,11 @@ import projInfo from "./data/projInfo.json";
import themes from "./data/themes"; import themes from "./data/themes";
import settings, { gameComponents } from "./game/settings"; import settings, { gameComponents } from "./game/settings";
import "./main.css"; import "./main.css";
import "@fontsource/roboto-mono";
import type { CSSProperties } from "vue";
const useHeader = projInfo.useHeader; const useHeader = projInfo.useHeader;
const theme = computed(() => themes[settings.theme].variables as CSSProperties); const theme = computed(() => themes[settings.theme].variables as CSSProperties);
const showTPS = toRef(settings, "showTPS"); const showTPS = toRef(settings, "showTPS");
const appErrors = toRef(state, "errors");
const gameComponent = computed(() => { const gameComponent = computed(() => {
return coerceComponent(jsx(() => (<>{gameComponents.map(render)}</>))); return coerceComponent(jsx(() => (<>{gameComponents.map(render)}</>)));
@ -51,4 +57,15 @@ const gameComponent = computed(() => {
height: 100%; height: 100%;
color: var(--foreground); color: var(--foreground);
} }
.error-container {
background: var(--background);
overflow: auto;
width: 100%;
height: 100%;
}
.error-container > .error {
position: static;
}
</style> </style>

103
src/components/Error.vue Normal file
View file

@ -0,0 +1,103 @@
<template>
<div class="error">
<h1 class="error-title">{{ firstError.name }}: {{ firstError.message }}</h1>
<div class="error-details" style="margin-top: -10px">
<div v-if="firstError.cause">
<div v-for="row in causes[0]" :key="row">{{ row }}</div>
</div>
<div v-if="firstError.stack" :style="firstError.cause ? 'margin-top: 10px' : ''">
<div v-for="row in stacks[0]" :key="row">{{ row }}</div>
</div>
</div>
<div class="instructions">
Check the console for more details, and consider sharing it with the developers on
<a :href="projInfo.discordLink || 'https://discord.gg/yJ4fjnjU54'" class="discord-link"
>discord</a
>!<br />
<div v-if="errors.length > 1" style="margin-top: 20px"><h3>Other errors</h3></div>
<div v-for="(error, i) in errors.slice(1)" :key="i" style="margin-top: 20px">
<details class="error-details">
<summary>{{ error.name }}: {{ error.message }}</summary>
<div v-if="error.cause" style="margin-top: 10px">
<div v-for="row in causes[i + 1]" :key="row">{{ row }}</div>
</div>
<div v-if="error.stack" style="margin-top: 10px">
<div v-for="row in stacks[i + 1]" :key="row">{{ row }}</div>
</div>
</details>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import projInfo from "data/projInfo.json";
import player from "game/player";
import { computed, onMounted } from "vue";
const props = defineProps<{
errors: Error[];
}>();
const firstError = computed(() => props.errors[0]);
const stacks = computed(() =>
props.errors.map(error => (error.stack == null ? [] : error.stack.split("\n")))
);
const causes = computed(() =>
props.errors.map(error =>
error.cause == null
? []
: (typeof error.cause === "string" ? error.cause : JSON.stringify(error.cause)).split(
"\n"
)
)
);
onMounted(() => {
player.autosave = false;
player.devSpeed = 0;
});
</script>
<style scoped>
.error {
border: solid 10px var(--danger);
position: absolute;
top: 0;
left: 0;
right: 0;
text-align: left;
min-height: calc(100% - 20px);
text-align: left;
color: var(--foreground);
}
.error-title {
background: var(--danger);
color: var(--feature-foreground);
display: block;
margin: -10px 0 10px 0;
position: sticky;
top: 0;
}
.error-details {
white-space: nowrap;
overflow: auto;
padding: 10px;
background-color: var(--raised-background);
}
.instructions {
padding: 10px;
}
.discord-link {
display: inline;
}
summary {
cursor: pointer;
user-select: none;
}
</style>

View file

@ -1,5 +1,6 @@
<template> <template>
<div class="layer-container" :style="{ '--layer-color': unref(color) }"> <ErrorVue v-if="errors.length > 0" :errors="errors" />
<div class="layer-container" :style="{ '--layer-color': unref(color) }" v-bind="$attrs" v-else>
<button v-if="showGoBack" class="goBack" @click="goBack"></button> <button v-if="showGoBack" class="goBack" @click="goBack"></button>
<button <button
@ -28,12 +29,12 @@ import type { CoercableComponent } from "features/feature";
import type { FeatureNode } from "game/layers"; import type { FeatureNode } from "game/layers";
import player from "game/player"; import player from "game/player";
import { computeComponent, computeOptionalComponent, processedPropType, unwrapRef } from "util/vue"; import { computeComponent, computeOptionalComponent, processedPropType, unwrapRef } from "util/vue";
import type { PropType, Ref } from "vue"; import { PropType, Ref, computed, defineComponent, onErrorCaptured, ref, toRefs, unref } from "vue";
import { computed, defineComponent, toRefs, unref } from "vue";
import Context from "./Context.vue"; import Context from "./Context.vue";
import ErrorVue from "./Error.vue";
export default defineComponent({ export default defineComponent({
components: { Context }, components: { Context, ErrorVue },
props: { props: {
index: { index: {
type: Number, type: Number,
@ -77,13 +78,23 @@ export default defineComponent({
props.nodes.value = nodes; props.nodes.value = nodes;
} }
const errors = ref<Error[]>([]);
onErrorCaptured((err, instance, info) => {
console.warn(`Error caught in "${props.name}" layer`, err, instance, info);
errors.value.push(
err instanceof Error ? (err as Error) : new Error(JSON.stringify(err))
);
return false;
});
return { return {
component, component,
minimizedComponent, minimizedComponent,
showGoBack, showGoBack,
updateNodes, updateNodes,
unref, unref,
goBack goBack,
errors
}; };
} }
}); });

View file

@ -459,7 +459,7 @@ export function createFormulaPreview(
const processedShowPreview = convertComputable(showPreview); const processedShowPreview = convertComputable(showPreview);
const processedPreviewAmount = convertComputable(previewAmount); const processedPreviewAmount = convertComputable(previewAmount);
if (!formula.hasVariable()) { if (!formula.hasVariable()) {
throw new Error("Cannot create formula preview if the formula does not have a variable"); console.error("Cannot create formula preview if the formula does not have a variable");
} }
return jsx(() => { return jsx(() => {
if (unref(processedShowPreview)) { if (unref(processedShowPreview)) {

View file

@ -151,8 +151,7 @@ export function createTabFamily<T extends TabFamilyOptions>(
optionsFunc?: OptionsFunc<T, BaseTabFamily, GenericTabFamily> optionsFunc?: OptionsFunc<T, BaseTabFamily, GenericTabFamily>
): TabFamily<T> { ): TabFamily<T> {
if (Object.keys(tabs).length === 0) { if (Object.keys(tabs).length === 0) {
console.warn("Cannot create tab family with 0 tabs"); console.error("Cannot create tab family with 0 tabs");
throw new Error("Cannot create tab family with 0 tabs");
} }
const selected = persistent(Object.keys(tabs)[0], false); const selected = persistent(Object.keys(tabs)[0], false);

View file

@ -2,7 +2,7 @@ import { Resource } from "features/resources/resource";
import { NonPersistent } from "game/persistence"; import { NonPersistent } from "game/persistence";
import Decimal, { DecimalSource, format } from "util/bignum"; import Decimal, { DecimalSource, format } from "util/bignum";
import { Computable, ProcessedComputable, convertComputable } from "util/computed"; import { Computable, ProcessedComputable, convertComputable } from "util/computed";
import { ComputedRef, Ref, computed, ref, unref } from "vue"; import { Ref, computed, ref, unref } from "vue";
import * as ops from "./operations"; import * as ops from "./operations";
import type { import type {
EvaluateFunction, EvaluateFunction,
@ -104,7 +104,7 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
private setupConstant({ inputs }: { inputs: [FormulaSource] }): InternalFormulaProperties<T> { private setupConstant({ inputs }: { inputs: [FormulaSource] }): InternalFormulaProperties<T> {
if (inputs.length !== 1) { if (inputs.length !== 1) {
throw new Error("Evaluate function is required if inputs is not length 1"); console.error("Evaluate function is required if inputs is not length 1");
} }
return { return {
inputs: inputs as T, inputs: inputs as T,
@ -250,7 +250,8 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
} }
return lhs.invert(value); return lhs.invert(value);
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
return new Formula({ return new Formula({
inputs: [value], inputs: [value],
@ -294,7 +295,8 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
!formula.isInvertible() || !formula.isInvertible() ||
(elseFormula != null && !elseFormula.isInvertible()) (elseFormula != null && !elseFormula.isInvertible())
) { ) {
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
if (unref(processedCondition)) { if (unref(processedCondition)) {
return lhs.invert(formula.invert(value)); return lhs.invert(formula.invert(value));
@ -1260,7 +1262,8 @@ export default class Formula<
} else if (this.inputs.length === 1 && this.hasVariable()) { } else if (this.inputs.length === 1 && this.hasVariable()) {
return value; return value;
} }
throw new Error("Cannot invert non-invertible formula"); console.error("Cannot invert non-invertible formula");
return 0;
} }
/** /**
@ -1270,7 +1273,8 @@ export default class Formula<
*/ */
evaluateIntegral(variable?: DecimalSource): DecimalSource { evaluateIntegral(variable?: DecimalSource): DecimalSource {
if (!this.isIntegrable()) { if (!this.isIntegrable()) {
throw new Error("Cannot evaluate integral of formula without integral"); console.error("Cannot evaluate integral of formula without integral");
return 0;
} }
return this.getIntegralFormula().evaluate(variable); return this.getIntegralFormula().evaluate(variable);
} }
@ -1282,7 +1286,8 @@ export default class Formula<
*/ */
invertIntegral(value: DecimalSource): DecimalSource { invertIntegral(value: DecimalSource): DecimalSource {
if (!this.isIntegrable() || !this.getIntegralFormula().isInvertible()) { if (!this.isIntegrable() || !this.getIntegralFormula().isInvertible()) {
throw new Error("Cannot invert integral of formula without invertible integral"); console.error("Cannot invert integral of formula without invertible integral");
return 0;
} }
return (this.getIntegralFormula() as InvertibleFormula).invert(value); return (this.getIntegralFormula() as InvertibleFormula).invert(value);
} }
@ -1309,7 +1314,8 @@ export default class Formula<
// We're the complex operation of this formula // We're the complex operation of this formula
stack = []; stack = [];
if (this.internalIntegrate == null) { if (this.internalIntegrate == null) {
throw new Error("Cannot integrate formula with non-integrable operation"); console.error("Cannot integrate formula with non-integrable operation");
return Formula.constant(0);
} }
let value = this.internalIntegrate.call(this, stack, ...this.inputs); let value = this.internalIntegrate.call(this, stack, ...this.inputs);
stack.forEach(func => (value = func(value))); stack.forEach(func => (value = func(value)));
@ -1329,14 +1335,15 @@ export default class Formula<
) { ) {
this.integralFormula = this; this.integralFormula = this;
} else { } else {
throw new Error("Cannot integrate formula without variable"); console.error("Cannot integrate formula without variable");
return Formula.constant(0);
} }
} }
return this.integralFormula; return this.integralFormula;
} else { } else {
// "Inner" part of the formula // "Inner" part of the formula
if (this.applySubstitution == null) { if (this.applySubstitution == null) {
throw new Error("Cannot have two complex operations in an integrable formula"); console.error("Cannot have two complex operations in an integrable formula");
} }
stack.push((variable: GenericFormula) => stack.push((variable: GenericFormula) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@ -1353,7 +1360,8 @@ export default class Formula<
) { ) {
return this; return this;
} else { } else {
throw new Error("Cannot integrate formula without variable"); console.error("Cannot integrate formula without variable");
return Formula.constant(0);
} }
} }
} }
@ -1428,15 +1436,17 @@ export function calculateMaxAffordable(
let affordable: DecimalSource = 0; let affordable: DecimalSource = 0;
if (Decimal.gt(maxBulkAmount, directSum)) { if (Decimal.gt(maxBulkAmount, directSum)) {
if (!formula.isInvertible()) { if (!formula.isInvertible()) {
throw new Error( console.error(
"Cannot calculate max affordable of non-invertible formula with more maxBulkAmount than directSum" "Cannot calculate max affordable of non-invertible formula with more maxBulkAmount than directSum"
); );
return 0;
} }
if (cumulativeCost) { if (cumulativeCost) {
if (!formula.isIntegralInvertible()) { if (!formula.isIntegralInvertible()) {
throw new Error( console.error(
"Cannot calculate max affordable of formula with non-invertible integral" "Cannot calculate max affordable of formula with non-invertible integral"
); );
return 0;
} }
affordable = Decimal.floor( affordable = Decimal.floor(
formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral())) formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral()))
@ -1517,13 +1527,15 @@ export function calculateCost(
// Indirect sum // Indirect sum
if (Decimal.gt(amountToBuy, directSum)) { if (Decimal.gt(amountToBuy, directSum)) {
if (!formula.isInvertible()) { if (!formula.isInvertible()) {
throw new Error("Cannot calculate cost with indirect sum of non-invertible formula"); console.error("Cannot calculate cost with indirect sum of non-invertible formula");
return 0;
} }
if (cumulativeCost) { if (cumulativeCost) {
if (!formula.isIntegrable()) { if (!formula.isIntegrable()) {
throw new Error( console.error(
"Cannot calculate cost with cumulative cost of non-integrable formula" "Cannot calculate cost with cumulative cost of non-integrable formula"
); );
return 0;
} }
cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral()); cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral());
if (targetValue.gt(1e308)) { if (targetValue.gt(1e308)) {

View file

@ -12,17 +12,20 @@ export function invertNeg(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.neg(value)); return lhs.invert(Decimal.neg(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateNeg(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateNeg(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
return Formula.neg(lhs.getIntegralFormula(stack)); return Formula.neg(lhs.getIntegralFormula(stack));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function applySubstitutionNeg(value: GenericFormula) { export function applySubstitutionNeg(value: GenericFormula) {
@ -35,24 +38,28 @@ export function invertAdd(value: DecimalSource, lhs: FormulaSource, rhs: Formula
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return rhs.invert(Decimal.sub(value, unrefFormulaSource(lhs))); return rhs.invert(Decimal.sub(value, unrefFormulaSource(lhs)));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateAdd(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) { export function integrateAdd(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.times(rhs, lhs.innermostVariable ?? 0).add(x); return Formula.times(rhs, lhs.innermostVariable ?? 0).add(x);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
if (!rhs.isIntegrable()) { if (!rhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = rhs.getIntegralFormula(stack); const x = rhs.getIntegralFormula(stack);
return Formula.times(lhs, rhs.innermostVariable ?? 0).add(x); return Formula.times(lhs, rhs.innermostVariable ?? 0).add(x);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function integrateInnerAdd( export function integrateInnerAdd(
@ -62,18 +69,21 @@ export function integrateInnerAdd(
) { ) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.add(x, rhs); return Formula.add(x, rhs);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
if (!rhs.isIntegrable()) { if (!rhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = rhs.getIntegralFormula(stack); const x = rhs.getIntegralFormula(stack);
return Formula.add(x, lhs); return Formula.add(x, lhs);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
@ -82,24 +92,28 @@ export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: Formula
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return rhs.invert(Decimal.sub(unrefFormulaSource(lhs), value)); return rhs.invert(Decimal.sub(unrefFormulaSource(lhs), value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateSub(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) { export function integrateSub(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.sub(x, Formula.times(rhs, lhs.innermostVariable ?? 0)); return Formula.sub(x, Formula.times(rhs, lhs.innermostVariable ?? 0));
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
if (!rhs.isIntegrable()) { if (!rhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = rhs.getIntegralFormula(stack); const x = rhs.getIntegralFormula(stack);
return Formula.times(lhs, rhs.innermostVariable ?? 0).sub(x); return Formula.times(lhs, rhs.innermostVariable ?? 0).sub(x);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function integrateInnerSub( export function integrateInnerSub(
@ -109,18 +123,21 @@ export function integrateInnerSub(
) { ) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.sub(x, rhs); return Formula.sub(x, rhs);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
if (!rhs.isIntegrable()) { if (!rhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = rhs.getIntegralFormula(stack); const x = rhs.getIntegralFormula(stack);
return Formula.sub(x, lhs); return Formula.sub(x, lhs);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
@ -129,24 +146,28 @@ export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: Formula
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return rhs.invert(Decimal.div(value, unrefFormulaSource(lhs))); return rhs.invert(Decimal.div(value, unrefFormulaSource(lhs)));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateMul(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) { export function integrateMul(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.times(x, rhs); return Formula.times(x, rhs);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
if (!rhs.isIntegrable()) { if (!rhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = rhs.getIntegralFormula(stack); const x = rhs.getIntegralFormula(stack);
return Formula.times(x, lhs); return Formula.times(x, lhs);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function applySubstitutionMul( export function applySubstitutionMul(
@ -159,7 +180,8 @@ export function applySubstitutionMul(
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return Formula.div(value, lhs); return Formula.div(value, lhs);
} }
throw new Error("Could not apply substitution due to no input being a variable"); console.error("Could not apply substitution due to no input being a variable");
return Formula.constant(0);
} }
export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
@ -168,24 +190,28 @@ export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: Formula
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return rhs.invert(Decimal.div(unrefFormulaSource(lhs), value)); return rhs.invert(Decimal.div(unrefFormulaSource(lhs), value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateDiv(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) { export function integrateDiv(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.div(x, rhs); return Formula.div(x, rhs);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
if (!rhs.isIntegrable()) { if (!rhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = rhs.getIntegralFormula(stack); const x = rhs.getIntegralFormula(stack);
return Formula.div(lhs, x); return Formula.div(lhs, x);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function applySubstitutionDiv( export function applySubstitutionDiv(
@ -198,32 +224,37 @@ export function applySubstitutionDiv(
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return Formula.mul(value, lhs); return Formula.mul(value, lhs);
} }
throw new Error("Could not apply substitution due to no input being a variable"); console.error("Could not apply substitution due to no input being a variable");
return Formula.constant(0);
} }
export function invertRecip(value: DecimalSource, lhs: FormulaSource) { export function invertRecip(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.recip(value)); return lhs.invert(Decimal.recip(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateRecip(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateRecip(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.ln(x); return Formula.ln(x);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertLog10(value: DecimalSource, lhs: FormulaSource) { export function invertLog10(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.pow10(value)); return lhs.invert(Decimal.pow10(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
function internalIntegrateLog10(lhs: DecimalSource) { function internalIntegrateLog10(lhs: DecimalSource) {
@ -235,13 +266,15 @@ function internalInvertIntegralLog10(value: DecimalSource, lhs: FormulaSource) {
const numerator = ln10.times(value); const numerator = ln10.times(value);
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw())); return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
return new Formula({ return new Formula({
inputs: [lhs.getIntegralFormula(stack)], inputs: [lhs.getIntegralFormula(stack)],
@ -249,7 +282,8 @@ export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
invert: internalInvertIntegralLog10 invert: internalInvertIntegralLog10
}); });
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
@ -258,7 +292,8 @@ export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: Formula
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value)); return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
function internalIntegrateLog(lhs: DecimalSource, rhs: DecimalSource) { function internalIntegrateLog(lhs: DecimalSource, rhs: DecimalSource) {
@ -270,13 +305,15 @@ function internalInvertIntegralLog(value: DecimalSource, lhs: FormulaSource, rhs
const numerator = Decimal.ln(unrefFormulaSource(rhs)).times(value); const numerator = Decimal.ln(unrefFormulaSource(rhs)).times(value);
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw())); return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) { export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
return new Formula({ return new Formula({
inputs: [lhs.getIntegralFormula(stack), rhs], inputs: [lhs.getIntegralFormula(stack), rhs],
@ -284,14 +321,16 @@ export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs:
invert: internalInvertIntegralLog invert: internalInvertIntegralLog
}); });
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertLog2(value: DecimalSource, lhs: FormulaSource) { export function invertLog2(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.pow(2, value)); return lhs.invert(Decimal.pow(2, value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
function internalIntegrateLog2(lhs: DecimalSource) { function internalIntegrateLog2(lhs: DecimalSource) {
@ -303,13 +342,15 @@ function internalInvertIntegralLog2(value: DecimalSource, lhs: FormulaSource) {
const numerator = Decimal.ln(2).times(value); const numerator = Decimal.ln(2).times(value);
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw())); return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
return new Formula({ return new Formula({
inputs: [lhs.getIntegralFormula(stack)], inputs: [lhs.getIntegralFormula(stack)],
@ -317,14 +358,16 @@ export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
invert: internalInvertIntegralLog2 invert: internalInvertIntegralLog2
}); });
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertLn(value: DecimalSource, lhs: FormulaSource) { export function invertLn(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.exp(value)); return lhs.invert(Decimal.exp(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
function internalIntegrateLn(lhs: DecimalSource) { function internalIntegrateLn(lhs: DecimalSource) {
@ -335,13 +378,15 @@ function internalInvertIntegralLn(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.div(value, Decimal.div(value, Math.E).lambertw())); return lhs.invert(Decimal.div(value, Decimal.div(value, Math.E).lambertw()));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
return new Formula({ return new Formula({
inputs: [lhs.getIntegralFormula(stack)], inputs: [lhs.getIntegralFormula(stack)],
@ -349,7 +394,8 @@ export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
invert: internalInvertIntegralLn invert: internalInvertIntegralLn
}); });
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
@ -358,43 +404,50 @@ export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: Formula
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return rhs.invert(Decimal.ln(value).div(Decimal.ln(unrefFormulaSource(lhs)))); return rhs.invert(Decimal.ln(value).div(Decimal.ln(unrefFormulaSource(lhs))));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integratePow(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) { export function integratePow(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
const pow = Formula.add(rhs, 1); const pow = Formula.add(rhs, 1);
return Formula.pow(x, pow).div(pow); return Formula.pow(x, pow).div(pow);
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
if (!rhs.isIntegrable()) { if (!rhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = rhs.getIntegralFormula(stack); const x = rhs.getIntegralFormula(stack);
return Formula.pow(lhs, x).div(Formula.ln(lhs)); return Formula.pow(lhs, x).div(Formula.ln(lhs));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertPow10(value: DecimalSource, lhs: FormulaSource) { export function invertPow10(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.root(value, 10)); return lhs.invert(Decimal.root(value, 10));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integratePow10(stack: SubstitutionStack, lhs: FormulaSource) { export function integratePow10(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.pow10(x).div(Formula.ln(10)); return Formula.pow10(x).div(Formula.ln(10));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
@ -403,25 +456,29 @@ export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: For
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value)); return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integratePowBase(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) { export function integratePowBase(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.pow(rhs, x).div(Formula.ln(rhs)); return Formula.pow(rhs, x).div(Formula.ln(rhs));
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
if (!rhs.isIntegrable()) { if (!rhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = rhs.getIntegralFormula(stack); const x = rhs.getIntegralFormula(stack);
const denominator = Formula.add(lhs, 1); const denominator = Formula.add(lhs, 1);
return Formula.pow(x, denominator).div(denominator); return Formula.pow(x, denominator).div(denominator);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) { export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
@ -430,36 +487,42 @@ export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: Formul
} else if (hasVariable(rhs)) { } else if (hasVariable(rhs)) {
return rhs.invert(Decimal.ln(unrefFormulaSource(lhs)).div(Decimal.ln(value))); return rhs.invert(Decimal.ln(unrefFormulaSource(lhs)).div(Decimal.ln(value)));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateRoot(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) { export function integrateRoot(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.pow(x, Formula.recip(rhs).add(1)).times(rhs).div(Formula.add(rhs, 1)); return Formula.pow(x, Formula.recip(rhs).add(1)).times(rhs).div(Formula.add(rhs, 1));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertExp(value: DecimalSource, lhs: FormulaSource) { export function invertExp(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.ln(value)); return lhs.invert(Decimal.ln(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateExp(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateExp(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.exp(x); return Formula.exp(x);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function tetrate( export function tetrate(
@ -481,7 +544,8 @@ export function invertTetrate(
return base.invert(Decimal.ssqrt(value)); return base.invert(Decimal.ssqrt(value));
} }
// Other params can't be inverted ATM // Other params can't be inverted ATM
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function iteratedexp( export function iteratedexp(
@ -509,7 +573,8 @@ export function invertIteratedExp(
); );
} }
// Other params can't be inverted ATM // Other params can't be inverted ATM
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function iteratedLog( export function iteratedLog(
@ -533,7 +598,8 @@ export function invertSlog(value: DecimalSource, lhs: FormulaSource, rhs: Formul
); );
} }
// Other params can't be inverted ATM // Other params can't be inverted ATM
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function layeradd(value: DecimalSource, diff: DecimalSource, base: DecimalSource) { export function layeradd(value: DecimalSource, diff: DecimalSource, base: DecimalSource) {
@ -556,21 +622,24 @@ export function invertLayeradd(
); );
} }
// Other params can't be inverted ATM // Other params can't be inverted ATM
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function invertLambertw(value: DecimalSource, lhs: FormulaSource) { export function invertLambertw(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.pow(Math.E, value).times(value)); return lhs.invert(Decimal.pow(Math.E, value).times(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function invertSsqrt(value: DecimalSource, lhs: FormulaSource) { export function invertSsqrt(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.tetrate(value, 2)); return lhs.invert(Decimal.tetrate(value, 2));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function pentate(value: DecimalSource, height: DecimalSource, payload: DecimalSource) { export function pentate(value: DecimalSource, height: DecimalSource, payload: DecimalSource) {
@ -582,226 +651,262 @@ export function invertSin(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.asin(value)); return lhs.invert(Decimal.asin(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateSin(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateSin(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.cos(x).neg(); return Formula.cos(x).neg();
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertCos(value: DecimalSource, lhs: FormulaSource) { export function invertCos(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.acos(value)); return lhs.invert(Decimal.acos(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateCos(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateCos(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.sin(x); return Formula.sin(x);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertTan(value: DecimalSource, lhs: FormulaSource) { export function invertTan(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.atan(value)); return lhs.invert(Decimal.atan(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateTan(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateTan(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.cos(x).ln().neg(); return Formula.cos(x).ln().neg();
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertAsin(value: DecimalSource, lhs: FormulaSource) { export function invertAsin(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.sin(value)); return lhs.invert(Decimal.sin(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateAsin(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateAsin(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.asin(x) return Formula.asin(x)
.times(x) .times(x)
.add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2)))); .add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertAcos(value: DecimalSource, lhs: FormulaSource) { export function invertAcos(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.cos(value)); return lhs.invert(Decimal.cos(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateAcos(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateAcos(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.acos(x) return Formula.acos(x)
.times(x) .times(x)
.sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2)))); .sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertAtan(value: DecimalSource, lhs: FormulaSource) { export function invertAtan(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.tan(value)); return lhs.invert(Decimal.tan(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateAtan(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateAtan(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.atan(x) return Formula.atan(x)
.times(x) .times(x)
.sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2)); .sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertSinh(value: DecimalSource, lhs: FormulaSource) { export function invertSinh(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.asinh(value)); return lhs.invert(Decimal.asinh(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateSinh(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateSinh(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.cosh(x); return Formula.cosh(x);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertCosh(value: DecimalSource, lhs: FormulaSource) { export function invertCosh(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.acosh(value)); return lhs.invert(Decimal.acosh(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateCosh(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateCosh(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.sinh(x); return Formula.sinh(x);
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertTanh(value: DecimalSource, lhs: FormulaSource) { export function invertTanh(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.atanh(value)); return lhs.invert(Decimal.atanh(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateTanh(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateTanh(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.cosh(x).ln(); return Formula.cosh(x).ln();
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertAsinh(value: DecimalSource, lhs: FormulaSource) { export function invertAsinh(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.sinh(value)); return lhs.invert(Decimal.sinh(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateAsinh(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateAsinh(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.asinh(x).times(x).sub(Formula.pow(x, 2).add(1).sqrt()); return Formula.asinh(x).times(x).sub(Formula.pow(x, 2).add(1).sqrt());
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertAcosh(value: DecimalSource, lhs: FormulaSource) { export function invertAcosh(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.cosh(value)); return lhs.invert(Decimal.cosh(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateAcosh(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateAcosh(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.acosh(x) return Formula.acosh(x)
.times(x) .times(x)
.sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt())); .sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt()));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function invertAtanh(value: DecimalSource, lhs: FormulaSource) { export function invertAtanh(value: DecimalSource, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
return lhs.invert(Decimal.tanh(value)); return lhs.invert(Decimal.tanh(value));
} }
throw new Error("Could not invert due to no input being a variable"); console.error("Could not invert due to no input being a variable");
return 0;
} }
export function integrateAtanh(stack: SubstitutionStack, lhs: FormulaSource) { export function integrateAtanh(stack: SubstitutionStack, lhs: FormulaSource) {
if (hasVariable(lhs)) { if (hasVariable(lhs)) {
if (!lhs.isIntegrable()) { if (!lhs.isIntegrable()) {
throw new Error("Could not integrate due to variable not being integrable"); console.error("Could not integrate due to variable not being integrable");
return Formula.constant(0);
} }
const x = lhs.getIntegralFormula(stack); const x = lhs.getIntegralFormula(stack);
return Formula.atanh(x) return Formula.atanh(x)
.times(x) .times(x)
.add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2)); .add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2));
} }
throw new Error("Could not integrate due to no input being a variable"); console.error("Could not integrate due to no input being a variable");
return Formula.constant(0);
} }
export function createPassthroughBinaryFormula( export function createPassthroughBinaryFormula(

View file

@ -225,7 +225,9 @@ export function createLayer<T extends LayerOptions>(
addingLayers[addingLayers.length - 1] == null || addingLayers[addingLayers.length - 1] == null ||
addingLayers[addingLayers.length - 1] !== id addingLayers[addingLayers.length - 1] !== id
) { ) {
throw `Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}`; throw new Error(
`Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}`
);
} }
addingLayers.pop(); addingLayers.pop();

View file

@ -116,12 +116,7 @@ function checkNaNAndWrite<T extends State>(persistent: Persistent<T>, value: T)
state.NaNPath = persistent[SaveDataPath]; state.NaNPath = persistent[SaveDataPath];
state.NaNPersistent = persistent as Persistent<DecimalSource>; state.NaNPersistent = persistent as Persistent<DecimalSource>;
} }
console.error( console.error(`Attempted to save NaN value to ${persistent[SaveDataPath]?.join(".")}`);
`Attempted to save NaN value to`,
persistent[SaveDataPath]?.join("."),
persistent
);
throw new Error("Attempted to set NaN value. See above for details");
} }
persistent[PersistentState].value = value; persistent[PersistentState].value = value;
} }
@ -292,8 +287,8 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
"." "."
)}\` when it's already present at \`${value[SaveDataPath].join( )}\` when it's already present at \`${value[SaveDataPath].join(
"." "."
)}\`. This can cause unexpected behavior when loading saves between updates.`, )}\`.`,
value "This can cause unexpected behavior when loading saves between updates."
); );
} }
value[SaveDataPath] = newPath; value[SaveDataPath] = newPath;
@ -368,9 +363,9 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
return; return;
} }
console.error( console.error(
`Created persistent ref in ${layer.id} without registering it to the layer! Make sure to include everything persistent in the returned object`, `Created persistent ref in ${layer.id} without registering it to the layer!`,
persistent, "Make sure to include everything persistent in the returned object.\n\nCreated at:\n" +
"\nCreated at:\n" + persistent[StackTrace] persistent[StackTrace]
); );
}); });
persistentRefs[layer.id].clear(); persistentRefs[layer.id].clear();

View file

@ -1,5 +1,5 @@
import type { DecimalSource } from "util/bignum"; import type { DecimalSource } from "util/bignum";
import { shallowReactive } from "vue"; import { reactive, shallowReactive } from "vue";
import type { Persistent } from "./persistence"; import type { Persistent } from "./persistence";
/** An object of global data that is not persistent. */ /** An object of global data that is not persistent. */
@ -12,6 +12,8 @@ export interface Transient {
NaNPath?: string[]; NaNPath?: string[];
/** The ref that was being set to NaN. */ /** The ref that was being set to NaN. */
NaNPersistent?: Persistent<DecimalSource>; NaNPersistent?: Persistent<DecimalSource>;
/** List of errors that have occurred, to show the user. */
errors: Error[];
} }
declare global { declare global {
@ -24,5 +26,6 @@ declare global {
export default window.state = shallowReactive<Transient>({ export default window.state = shallowReactive<Transient>({
lastTenTicks: [], lastTenTicks: [],
hasNaN: false, hasNaN: false,
NaNPath: [] NaNPath: [],
errors: reactive([])
}); });

View file

@ -2,6 +2,7 @@ import "@fontsource/material-icons";
import App from "App.vue"; import App from "App.vue";
import projInfo from "data/projInfo.json"; import projInfo from "data/projInfo.json";
import "game/notifications"; import "game/notifications";
import state from "game/state";
import { load } from "util/save"; import { load } from "util/save";
import { useRegisterSW } from "virtual:pwa-register/vue"; import { useRegisterSW } from "virtual:pwa-register/vue";
import type { App as VueApp } from "vue"; import type { App as VueApp } from "vue";
@ -23,11 +24,30 @@ declare global {
} }
} }
const error = console.error;
console.error = function (...args) {
if (import.meta.env.DEV) {
state.errors.push(new Error(args[0], { cause: args[1] }));
}
error(...args);
};
window.onerror = function (event, source, lineno, colno, error) {
state.errors.push(error instanceof Error ? error : new Error(JSON.stringify(error)));
return true;
};
window.onunhandledrejection = function (event) {
state.errors.push(
event.reason instanceof Error ? event.reason : new Error(JSON.stringify(event.reason))
);
};
document.title = projInfo.title; document.title = projInfo.title;
window.projInfo = projInfo; window.projInfo = projInfo;
if (projInfo.id === "") { if (projInfo.id === "") {
throw new Error( console.error(
"Project ID is empty! Please select a unique ID for this project in /src/data/projInfo.json" "Project ID is empty!",
"Please select a unique ID for this project in /src/data/projInfo.json"
); );
} }
@ -43,6 +63,9 @@ requestAnimationFrame(async () => {
// Create Vue // Create Vue
const vue = (window.vue = createApp(App)); const vue = (window.vue = createApp(App));
vue.config.errorHandler = function (err, instance, info) {
console.error(err, info, instance);
};
globalBus.emit("setupVue", vue); globalBus.emit("setupVue", vue);
vue.mount("#app"); vue.mount("#app");

View file

@ -40,7 +40,7 @@ export function createLazyProxy<T extends object, S extends T>(
function calculateObj(): T { function calculateObj(): T {
if (!calculated) { if (!calculated) {
if (calculating) { if (calculating) {
throw new Error("Cyclical dependency detected. Cannot evaluate lazy proxy."); console.error("Cyclical dependency detected. Cannot evaluate lazy proxy.");
} }
calculating = true; calculating = true;
Object.assign(obj, objectFunc.call(obj, obj)); Object.assign(obj, objectFunc.call(obj, obj));