Skip to main content
Domain logic modules contain pure functions for calculations, filtering, and business rules. They are located in src/domain/ and are framework-agnostic.

unlock.ts

Determines whether tasks are unlocked based on player level and prerequisite completion.

isTaskUnlocked

Checks if a task is currently unlocked.
function isTaskUnlocked(
  task: QuestModel,
  playerLevel: number,
  progressMap: Map<string, TaskStatus>,
): boolean
task
QuestModel
The quest to check
playerLevel
number
Current player level (1-79)
progressMap
Map<string, TaskStatus>
Map of taskId → current status
returns
boolean
true if unlocked, false if locked by level or prerequisites
Unlock Conditions:
  1. playerLevel >= task.minPlayerLevel
  2. All prerequisite tasks have status 'done'
Example:
import { isTaskUnlocked } from '@/domain/unlock';

const unlocked = isTaskUnlocked(
  quest,
  currentLevel,
  new Map(progress.map(p => [p.taskId, p.status]))
);

getTaskLockState

Returns why a task is locked (or unlocked).
type TaskLockState = 'unlocked' | 'level_locked' | 'prereq_locked';

function getTaskLockState(
  task: QuestModel,
  playerLevel: number,
  progressMap: Map<string, TaskStatus>,
): TaskLockState
unlocked
TaskLockState
Task is available
level_locked
TaskLockState
Player level is too low
prereq_locked
TaskLockState
Prerequisite tasks not completed

partitionByLock

Splits quests into unlocked and locked lists.
function partitionByLock(
  quests: QuestModel[],
  playerLevel: number,
  progressMap: Map<string, TaskStatus>,
): { unlocked: QuestModel[]; locked: QuestModel[] }

findAutoStartCandidates

Finds tasks that can be auto-started (not started + unlocked).
function findAutoStartCandidates(
  quests: QuestModel[],
  playerLevel: number,
  progressMap: Map<string, TaskStatus>,
): string[]  // Returns taskIds

kappaProgress.ts

Calculates Kappa container progress statistics.

calcKappaProgress

Computes overall Kappa completion.
interface KappaStats {
  total: number;
  done: number;
  remaining: number;
  percent: number;
}

function calcKappaProgress(
  quests: QuestModel[],
  progressMap: Map<string, TaskStatus>,
): KappaStats
quests
QuestModel[]
All quests (filters for kappaRequired: true internally)
progressMap
Map<string, TaskStatus>
Current progress state
total
number
Total Kappa-required tasks
done
number
Completed Kappa tasks
remaining
number
Tasks left to complete
percent
number
Completion percentage (0-100)
Example:
import { calcKappaProgress } from '@/domain/kappaProgress';

const stats = calcKappaProgress(quests, progressMap);
console.log(`Kappa Progress: ${stats.done}/${stats.total} (${stats.percent}%)`);

calcKappaByTrader

Breaks down Kappa progress by trader.
interface TraderKappaStats {
  traderId: string;
  traderName: string;
  total: number;
  done: number;
  percent: number;
}

function calcKappaByTrader(
  quests: QuestModel[],
  progressMap: Map<string, TaskStatus>,
): TraderKappaStats[]
Example:
const byTrader = calcKappaByTrader(quests, progressMap);
byTrader.forEach(t => {
  console.log(`${t.traderName}: ${t.done}/${t.total} (${t.percent}%)`);
});

recommendations.ts

Generates task recommendations based on downstream impact, map batching, and Kappa priority.

Scoring Algorithm

Composite score = (downstreamNorm × 0.5) + (mapBatchNorm × 0.3) + (kappaNorm × 0.2)
  • Downstream Impact (50%): Number of future Kappa tasks unlocked
  • Map Batching (30%): Other tasks on the same map
  • Kappa Required (20%): Is this task required for Kappa?

getRecommendations

Returns top recommended tasks and map batch groups.
interface TaskRecommendation {
  quest: QuestModel;
  compositeScore: number;
  downstreamCount: number;      // How many future tasks this unlocks
  mapBatchCount: number;        // Other tasks on same map
  isKappaRequired: boolean;
  reasons: string[];            // Human-readable reasons
}

interface MapBatchGroup {
  mapId: string;
  mapName: string;
  tasks: TaskRecommendation[];
  count: number;
}

interface RecommendationResult {
  topTasks: TaskRecommendation[];    // Top 5 by score
  mapBatches: MapBatchGroup[];       // Maps with 2+ tasks
}

function getRecommendations(
  quests: QuestModel[],
  prereqEdges: Map<string, string[]>,
  progressMap: Map<string, TaskStatus>,
  playerLevel: number,
  mapNames?: Map<string, string>,
): RecommendationResult
quests
QuestModel[]
All quests
prereqEdges
Map<string, string[]>
Prerequisite graph (taskId → prereqIds)
progressMap
Map<string, TaskStatus>
Current progress
playerLevel
number
Current player level
mapNames
Map<string, string>
Map ID to display name lookup
Example:
import { getRecommendations } from '@/domain/recommendations';

const { topTasks, mapBatches } = getRecommendations(
  quests,
  prereqEdges,
  progressMap,
  playerLevel,
  mapNames
);

// Display top recommendations
topTasks.forEach((rec, i) => {
  console.log(`${i + 1}. ${rec.quest.name} (score: ${rec.compositeScore.toFixed(2)})`);
  console.log(`   Reasons: ${rec.reasons.join(', ')}`);
});

// Display map batches
mapBatches.forEach(batch => {
  console.log(`${batch.mapName}: ${batch.count} tasks`);
});

Helper Functions

buildReverseDependencyGraph

Inverts the prerequisite graph to find dependent tasks.
function buildReverseDependencyGraph(
  prereqEdges: Map<string, string[]>,
): Map<string, string[]>  // taskId → dependentTaskIds

countDownstreamTasks

Counts how many incomplete tasks depend on a given task (BFS with cycle guard).
function countDownstreamTasks(
  taskId: string,
  reverseDeps: Map<string, string[]>,
  progressMap: Map<string, TaskStatus>,
  kappaTaskIds?: Set<string>,
): number

getTaskMapIds

Extracts all maps associated with a task (from quest.mapId and objectives).
function getTaskMapIds(quest: QuestModel): string[]

taskFilters.ts

Filtering and enrichment logic for the Tasks page.

enrichTasks

Adds status and lock state to each quest.
interface EnrichedTask {
  quest: QuestModel;
  status: TaskStatus;
  lockState: TaskLockState;
}

function enrichTasks(
  quests: QuestModel[],
  playerLevel: number,
  progressMap: Map<string, TaskStatus>,
): EnrichedTask[]

filterTasks

Applies filters and sorts tasks for the UI.
interface FilterParams {
  traders: string[];
  maps: string[];
  types: string[];           // Objective types
  statuses: TaskStatus[];
  search: string;
  kappaOnly: boolean;
}

function filterTasks(
  tasks: EnrichedTask[],
  filters: FilterParams,
): EnrichedTask[]
Sort Order:
  1. Unlocked tasks first
  2. Then by minPlayerLevel (ascending)
  3. Then by name (alphabetical)
Example:
import { enrichTasks, filterTasks } from '@/domain/taskFilters';
import { useFilterStore } from '@/stores/filterStore';

const enriched = enrichTasks(quests, playerLevel, progressMap);
const filters = useFilterStore();
const filtered = filterTasks(enriched, filters);

prereqTree.ts

Builds prerequisite dependency trees for UI visualization.

collectIncompletePrereqs

Recursively collects incomplete prerequisites in dependency order (DFS).
function collectIncompletePrereqs(
  taskId: string,
  prereqEdges: Map<string, string[]>,
  progressMap: Map<string, TaskStatus>,
): string[]  // Deepest prereqs first

buildPrereqTree

Builds a tree structure of incomplete prerequisites.
interface PrereqNode {
  taskId: string;
  name: string;
  status: TaskStatus;
  children: PrereqNode[];
}

function buildPrereqTree(
  taskId: string,
  prereqEdges: Map<string, string[]>,
  progressMap: Map<string, TaskStatus>,
  nameMap: Map<string, string>,
): PrereqNode[]
taskId
string
The task to build tree for
prereqEdges
Map<string, string[]>
Prerequisite graph
progressMap
Map<string, TaskStatus>
Current progress
nameMap
Map<string, string>
taskId → display name lookup
Example:
import { buildPrereqTree } from '@/domain/prereqTree';

const tree = buildPrereqTree(taskId, prereqEdges, progressMap, nameMap);

function renderTree(nodes: PrereqNode[], depth = 0) {
  nodes.forEach(node => {
    console.log('  '.repeat(depth) + `${node.name} [${node.status}]`);
    renderTree(node.children, depth + 1);
  });
}

renderTree(tree);

nextUnlock.ts

Finds tasks unlocking soon (level-locked but prereqs met).

getNextUnlocks

Returns tasks unlocking within a level range.
interface NextUnlockGroup {
  level: number;
  tasks: QuestModel[];
}

function getNextUnlocks(
  quests: QuestModel[],
  playerLevel: number,
  progressMap: Map<string, TaskStatus>,
  levelRange = 5,
): NextUnlockGroup[]
levelRange
number
default:"5"
How many levels ahead to check
Example:
import { getNextUnlocks } from '@/domain/nextUnlock';

const upcoming = getNextUnlocks(quests, playerLevel, progressMap, 5);

upcoming.forEach(group => {
  console.log(`Level ${group.level}: ${group.tasks.length} tasks`);
  group.tasks.forEach(t => console.log(`  - ${t.name}`));
});

hideoutUnlock.ts

Hideout station build state and unlock logic.

isLevelBuildable

Checks if a hideout level can be built.
function isLevelBuildable(
  level: HideoutLevelModel,
  builtLevelIds: Set<string>,
  hideout: NormalizedHideout,
): boolean
Build Conditions:
  1. Previous level of same station is built
  2. All stationLevelRequirements are built
Item, skill, and trader requirements are not checked—they are display-only.

getHideoutBuildState

Returns build state for a specific level.
type HideoutBuildState = 'built' | 'buildable' | 'locked';

function getHideoutBuildState(
  level: HideoutLevelModel,
  builtLevelIds: Set<string>,
  hideout: NormalizedHideout,
): HideoutBuildState

getStationCurrentLevel

Returns the highest built level for a station (0 = not built).
function getStationCurrentLevel(
  station: HideoutStationModel,
  builtLevelIds: Set<string>,
): number

getNextBuildableLevel

Returns the next level that can be built, or null if maxed.
function getNextBuildableLevel(
  station: HideoutStationModel,
  builtLevelIds: Set<string>,
): HideoutLevelModel | null

getStationBuildState

Returns overall build state for a station.
type StationBuildState = 'all_built' | 'has_buildable' | 'locked';

function getStationBuildState(
  station: HideoutStationModel,
  builtLevelIds: Set<string>,
  hideout: NormalizedHideout,
): StationBuildState

calcHideoutProgress

Calculates overall hideout completion.
function calcHideoutProgress(
  hideout: NormalizedHideout,
  builtLevelIds: Set<string>,
): { totalLevels: number; builtCount: number; percent: number }
Example:
import { calcHideoutProgress } from '@/domain/hideoutUnlock';

const progress = calcHideoutProgress(hideout, builtLevelIds);
console.log(`Hideout: ${progress.builtCount}/${progress.totalLevels} (${progress.percent}%)`);

itemTier.ts

Item filtering, sorting, and tier grouping.

Tier Configuration

export const TIER_ORDER: PriceTier[] = ['S', 'A', 'B', 'C', 'D'];

export interface TierConfig {
  label: string;
  color: string;
  bgColor: string;
  description: string;
}

buildTierConfig

Builds tier configuration from custom thresholds.
function buildTierConfig(thresholds: TierThresholds): Record<PriceTier, TierConfig>

filterItems

Applies filters and sorting to items.
interface ItemFilters {
  search: string;
  types: string[];
  tiers: PriceTier[];
  taskRelations: TaskRelation[];
  kappaTaskIds?: Set<string>;
  collectorTaskId?: string | null;
  sortBy: 'pricePerSlot' | 'bestSellPrice' | 'name';
  sortDir: 'asc' | 'desc';
}

function filterItems(items: ItemModel[], filters: ItemFilters): ItemModel[]
Task Relations:
  • 'usedInKappaTask': Item is required for a Kappa task
  • 'rewardFromTask': Item is a task reward
  • 'collector': Item is needed for Collector quest

groupByTier

Groups items by price tier.
function groupByTier(items: ItemModel[]): Map<PriceTier, ItemModel[]>
Example:
import { filterItems, groupByTier, TIER_ORDER } from '@/domain/itemTier';

const filtered = filterItems(items, filters);
const grouped = groupByTier(filtered);

TIER_ORDER.forEach(tier => {
  const tierItems = grouped.get(tier)!;
  console.log(`${tier} Tier: ${tierItems.length} items`);
});

Build docs developers (and LLMs) love