feat: add titles system with unlock tracking and character sheet display

Titles are earned by reaching milestones (quests, bosses, gold, clicks,
adventurers, guild, prestige, transcendence, apotheosis, achievements,
longevity) and are permanent - never lost on prestige/transcendence/
apotheosis resets. 20 titles available at launch.

Also fixes a pre-existing P2034 write-conflict on the load backfill path
and the exactOptionalPropertyTypes violation in the quest failure handler.
This commit is contained in:
2026-03-07 14:51:30 -08:00
committed by Naomi Carrigan
parent eef807343b
commit b886928e49
13 changed files with 333 additions and 10 deletions
+1
View File
@@ -89,3 +89,4 @@ export type {
TranscendenceUpgrade,
TranscendenceUpgradeCategory,
} from "./interfaces/Transcendence.js";
export type { Title, TitleCondition, TitleConditionType } from "./interfaces/Title.js";
+13 -6
View File
@@ -118,17 +118,23 @@ export interface PublicProfileResponse {
questsCompleted: number;
adventurersRecruited: number;
achievementsUnlocked: number;
/** Titles this player has unlocked, as {id, name} pairs for display */
unlockedTitles: Array<{ id: string; name: string }>;
/** The player's active title display name (empty string if none set) */
activeTitle: string;
}
export interface UpdateProfileRequest {
characterName: string;
pronouns: string;
characterRace: string;
characterClass: string;
bio: string;
guildName: string;
guildDescription: string;
pronouns?: string;
characterRace?: string;
characterClass?: string;
bio?: string;
guildName?: string;
guildDescription?: string;
profileSettings: ProfileSettings;
/** Title ID to set as active (empty string to clear) */
activeTitle?: string;
}
export interface UpdateProfileResponse {
@@ -139,6 +145,7 @@ export interface UpdateProfileResponse {
bio: string;
guildName: string;
guildDescription: string;
activeTitle: string;
profileSettings: ProfileSettings;
}
+26
View File
@@ -0,0 +1,26 @@
export type TitleConditionType =
| "totalClicks"
| "totalGoldEarned"
| "bossesDefeated"
| "questsCompleted"
| "prestigeCount"
| "transcendenceCount"
| "apotheosisCount"
| "adventurerTotal"
| "achievementsUnlocked"
| "guildFounded"
| "playedDays";
export interface TitleCondition {
type: TitleConditionType;
/** Threshold required to unlock (not used for guildFounded) */
amount?: number;
}
export interface Title {
id: string;
name: string;
/** Human-readable description shown as the unlock hint */
description: string;
condition: TitleCondition;
}