Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/__tests__/observer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const mockCompanion: Companion = {
level: 1,
xp: 0,
mood: 'happy',
availablePoints: 0,
hatchedAt: Date.now(),
};

Expand Down
1 change: 1 addition & 0 deletions src/__tests__/reasoning/observer-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const mockCompanion: Companion = {
level: 1,
xp: 0,
mood: 'happy',
availablePoints: 0,
hatchedAt: Date.now(),
};

Expand Down
1 change: 1 addition & 0 deletions src/__tests__/reasoning/scrub-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const mock: Companion = {
level: 1,
xp: 0,
mood: 'happy',
availablePoints: 0,
hatchedAt: Date.now(),
};

Expand Down
14 changes: 14 additions & 0 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export function initDb() {
mood TEXT DEFAULT 'happy',
personality_bio TEXT DEFAULT '',
user_id TEXT,
stat_debugging INTEGER,
stat_patience INTEGER,
stat_chaos INTEGER,
stat_wisdom INTEGER,
stat_snark INTEGER,
stat_points_available INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Expand Down Expand Up @@ -79,6 +85,14 @@ export function initDb() {
db.exec(`ALTER TABLE companions ADD COLUMN cc_rescue INTEGER DEFAULT 0`);
} catch { /* column already exists */ }

// Migration: add stat columns for growth
try { db.exec(`ALTER TABLE companions ADD COLUMN stat_debugging INTEGER`); } catch {}
try { db.exec(`ALTER TABLE companions ADD COLUMN stat_patience INTEGER`); } catch {}
try { db.exec(`ALTER TABLE companions ADD COLUMN stat_chaos INTEGER`); } catch {}
try { db.exec(`ALTER TABLE companions ADD COLUMN stat_wisdom INTEGER`); } catch {}
try { db.exec(`ALTER TABLE companions ADD COLUMN stat_snark INTEGER`); } catch {}
try { db.exec(`ALTER TABLE companions ADD COLUMN stat_points_available INTEGER DEFAULT 0`); } catch {}

// Reasoning-layer migration (claims/edges tables + guard_mode column).
initReasoningSchema(db);
}
24 changes: 22 additions & 2 deletions src/lib/companion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,30 @@ export function loadCompanion(row: any, userIdOverride?: string): Companion | nu
const xp = row.xp || 0;
const derivedLevel = levelFromXp(xp);

// Stats priority: DB values > bones fallback
const stats = {
DEBUGGING: row.stat_debugging ?? bones.stats.DEBUGGING,
PATIENCE: row.stat_patience ?? bones.stats.PATIENCE,
CHAOS: row.stat_chaos ?? bones.stats.CHAOS,
WISDOM: row.stat_wisdom ?? bones.stats.WISDOM,
SNARK: row.stat_snark ?? bones.stats.SNARK,
};

// Self-healing: if DB level drifted from XP-derived level, fix it
if (row.id && row.level !== derivedLevel) {
db.prepare('UPDATE companions SET level = ? WHERE id = ?').run(derivedLevel, row.id);
}

return {
...bones,
stats,
species: row.species,
name: row.name,
personalityBio: row.personality_bio || '',
level: derivedLevel,
xp,
mood: row.mood,
availablePoints: row.stat_points_available || 0,
hatchedAt: new Date(row.created_at).getTime(),
};
}
Expand All @@ -76,6 +87,7 @@ export function writeBuddyStatus(companion: Companion, reaction?: { state: strin
eye: companion.eye,
hat: companion.hat,
stats: companion.stats,
available_points: companion.availablePoints,
rarity_stars: RARITY_STARS[companion.rarity],
personality_bio: companion.personalityBio,
...(reaction ? {
Expand Down Expand Up @@ -113,8 +125,14 @@ export function createCompanion(opts: {
const bio = generateBio({ ...bones, species: finalSpecies });

db.prepare(
'INSERT INTO companions (id, name, species, user_id, personality_bio) VALUES (?, ?, ?, ?, ?)'
).run(id, finalName, finalSpecies, userId, bio);
`INSERT INTO companions (
id, name, species, user_id, personality_bio,
stat_debugging, stat_patience, stat_chaos, stat_wisdom, stat_snark
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run(
id, finalName, finalSpecies, userId, bio,
bones.stats.DEBUGGING, bones.stats.PATIENCE, bones.stats.CHAOS, bones.stats.WISDOM, bones.stats.SNARK
);

const companion: Companion = {
...bones,
Expand All @@ -124,6 +142,7 @@ export function createCompanion(opts: {
level: 1,
xp: 0,
mood: 'happy',
availablePoints: 0,
hatchedAt: Date.now(),
};

Expand Down Expand Up @@ -196,6 +215,7 @@ export function rescueCompanion(importResult: {
level: 1,
xp: 0,
mood: 'happy',
availablePoints: 0,
hatchedAt,
};

Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type Companion = CompanionBones & CompanionSoul & {
level: number;
xp: number;
mood: string;
availablePoints: number;
hatchedAt: number;
};

Expand Down
11 changes: 9 additions & 2 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,19 @@ function awardXp(companionId: string, eventType: string): { newXp: number; newLe
db.prepare("INSERT INTO xp_events (id, companion_id, event_type, xp_gained) VALUES (?, ?, ?, ?)").run(id, companionId, eventType, xp);

// Get current total XP
const row = db.prepare("SELECT xp, level FROM companions WHERE id = ?").get(companionId) as any;
const row = db.prepare("SELECT xp, level, stat_points_available FROM companions WHERE id = ?").get(companionId) as any;
const newXp = (row?.xp || 0) + xp;
const newLevel = levelFromXp(newXp);
const leveledUp = newLevel > (row?.level || 1);

db.prepare("UPDATE companions SET xp = ?, level = ? WHERE id = ?").run(newXp, newLevel, companionId);
// Award +2 stat points per level gained
if (leveledUp) {
const levelsGained = newLevel - (row?.level || 1);
const newPoints = (row?.stat_points_available || 0) + (levelsGained * 2);
db.prepare("UPDATE companions SET xp = ?, level = ?, stat_points_available = ? WHERE id = ?").run(newXp, newLevel, newPoints, companionId);
} else {
db.prepare("UPDATE companions SET xp = ?, level = ? WHERE id = ?").run(newXp, newLevel, companionId);
}

return { newXp, newLevel, leveledUp };
}
Expand Down