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
Current player level (1-79)
Map of taskId → current status
true if unlocked, false if locked by level or prerequisites
Unlock Conditions:
playerLevel >= task.minPlayerLevel
- 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
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
All quests (filters for kappaRequired: true internally)
Total Kappa-required tasks
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
Prerequisite graph (taskId → prereqIds)
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:
- Unlocked tasks first
- Then by
minPlayerLevel (ascending)
- 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[]
The task to build tree for
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[]
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:
- Previous level of same station is built
- 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`);
});