fightbook 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/engine/FightEngine.ts"],
4
+ "sourcesContent": ["// FightBook - MMA Combat Engine\n// Real-time fight simulation with authentic MMA techniques\n\nimport {\n FighterStats,\n FighterState,\n FightAction,\n FightState,\n RoundState,\n ActionType,\n Technique,\n STRIKING_TECHNIQUES,\n GRAPPLING_TECHNIQUES,\n SUBMISSION_TECHNIQUES,\n GROUND_TECHNIQUES,\n ROUND_DURATION,\n TOTAL_ROUNDS,\n DEFAULT_FIGHTER,\n} from '@/types/fight';\n\nexport interface GameState {\n round: number;\n timeRemaining: number;\n myHealth: number;\n myStamina: number;\n myPosition: 'standing' | 'clinch' | 'ground_top' | 'ground_bottom';\n oppHealth: number;\n oppStamina: number;\n oppPosition: 'standing' | 'clinch' | 'ground_top' | 'ground_bottom';\n recentActions: string[];\n}\n\nexport class FightEngine {\n private fight: FightState;\n private intervalId: NodeJS.Timeout | null = null;\n private onAction: ((action: FightAction) => void) | null = null;\n private onRoundEnd: ((round: RoundState) => void) | null = null;\n private onFightEnd: ((fight: FightState) => void) | null = null;\n private tickRate: number = 1000; // 1 second per tick for real-time feel\n \n // LLM and CPU callbacks\n private llmCallback?: (actor: string, target: string, gameState: GameState) => Promise<string>;\n private cpuCallback?: (actor: string, target: string, gameState: GameState) => string;\n private mixedMode: boolean = false;\n private llmFighterId: string = 'fighter1'; // Which fighter uses LLM\n\n constructor(\n fighter1Stats: FighterStats,\n fighter2Stats: FighterStats,\n callbacks?: {\n onAction?: (action: FightAction) => void;\n onRoundEnd?: (round: RoundState) => void;\n onFightEnd?: (fight: FightState) => void;\n llmCallback?: (actor: string, target: string, gameState: GameState) => Promise<string>;\n cpuCallback?: (actor: string, target: string, gameState: GameState) => string;\n mixedMode?: boolean;\n llmFighterId?: string;\n }\n ) {\n this.fight = this.initializeFight(fighter1Stats, fighter2Stats);\n this.onAction = callbacks?.onAction || null;\n this.onRoundEnd = callbacks?.onRoundEnd || null;\n this.onFightEnd = callbacks?.onFightEnd || null;\n this.llmCallback = callbacks?.llmCallback;\n this.cpuCallback = callbacks?.cpuCallback;\n this.mixedMode = callbacks?.mixedMode || false;\n this.llmFighterId = callbacks?.llmFighterId || 'fighter1';\n }\n\n private initializeFight(f1: FighterStats, f2: FighterStats): FightState {\n const fighter1: FighterState = {\n ...f1,\n id: 'fighter1',\n currentHealth: 100,\n currentStamina: 100,\n position: 'standing',\n hasMount: false,\n hasBack: false,\n isGrounded: false,\n cuts: 0,\n knockdowns: 0,\n takedownsLanded: 0,\n takedownsAttempted: 0,\n significantStrikes: 0,\n totalStrikes: 0,\n };\n\n const fighter2: FighterState = {\n ...f2,\n id: 'fighter2',\n currentHealth: 100,\n currentStamina: 100,\n position: 'standing',\n hasMount: false,\n hasBack: false,\n isGrounded: false,\n cuts: 0,\n knockdowns: 0,\n takedownsLanded: 0,\n takedownsAttempted: 0,\n significantStrikes: 0,\n totalStrikes: 0,\n };\n\n return {\n id: Math.random().toString(36).substring(7),\n fighter1,\n fighter2,\n currentRound: 1,\n rounds: [this.createRound(1)],\n isComplete: false,\n };\n }\n\n private createRound(roundNum: number): RoundState {\n return {\n round: roundNum,\n timeRemaining: ROUND_DURATION,\n isActive: false,\n actions: [],\n };\n }\n\n // Start the fight\n start(): void {\n if (this.intervalId) return;\n this.fight.rounds[this.fight.currentRound - 1].isActive = true;\n this.intervalId = setInterval(() => this.tick(), this.tickRate);\n }\n\n // Pause the fight\n pause(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n }\n\n // Resume the fight\n resume(): void {\n if (!this.intervalId && !this.fight.isComplete) {\n this.intervalId = setInterval(() => this.tick(), this.tickRate);\n }\n }\n\n // Stop the fight completely\n stop(): void {\n this.pause();\n this.fight.isComplete = true;\n }\n\n // Get current fight state\n getState(): FightState {\n return { ...this.fight };\n }\n\n // Get simplified game state for LLM/CPU context\n getGameState(actorId: string): GameState {\n const actor = actorId === 'fighter1' ? this.fight.fighter1 : this.fight.fighter2;\n const target = actorId === 'fighter1' ? this.fight.fighter2 : this.fight.fighter1;\n const currentRound = this.fight.rounds[this.fight.currentRound - 1];\n \n const recentActions = currentRound?.actions.slice(-3).map(a => a.description) || [];\n \n return {\n round: this.fight.currentRound,\n timeRemaining: currentRound?.timeRemaining || 0,\n myHealth: actor.currentHealth,\n myStamina: actor.currentStamina,\n myPosition: actor.position,\n oppHealth: target.currentHealth,\n oppStamina: target.currentStamina,\n oppPosition: target.position,\n recentActions,\n };\n }\n\n // Main game loop tick - runs every second\n private tick(): void {\n const currentRound = this.fight.rounds[this.fight.currentRound - 1];\n \n if (currentRound.timeRemaining <= 0) {\n this.endRound();\n return;\n }\n\n // Decrement time\n currentRound.timeRemaining--;\n\n // Recover stamina slightly each second (passive recovery)\n this.recoverStamina();\n\n // Determine if action happens this tick (not every second has action)\n const actionChance = 0.7 + (this.fight.fighter1.aggression + this.fight.fighter2.aggression) / 2;\n if (Math.random() < actionChance) {\n // Pick which fighter acts (weighted by aggression)\n const f1Aggression = this.fight.fighter1.aggression;\n const f2Aggression = this.fight.fighter2.aggression;\n const total = f1Aggression + f2Aggression || 1;\n \n const actingFighter = Math.random() < (f1Aggression / total) ? 'fighter1' : 'fighter2';\n const action = this.generateAction(actingFighter);\n \n if (action) {\n currentRound.actions.push(action);\n this.applyAction(action);\n this.onAction?.(action);\n\n // Check for fight ending conditions\n if (this.checkFightEnd()) {\n this.endFight();\n }\n }\n }\n\n // Check for exhaustion TKO\n if (this.checkExhaustion()) {\n this.endFight();\n }\n }\n\n private recoverStamina(): void {\n const recovery1 = (this.fight.fighter1.recovery / 100) * 0.3;\n const recovery2 = (this.fight.fighter2.recovery / 100) * 0.3;\n \n this.fight.fighter1.currentStamina = Math.min(100, this.fight.fighter1.currentStamina + recovery1);\n this.fight.fighter2.currentStamina = Math.min(100, this.fight.fighter2.currentStamina + recovery2);\n }\n\n private generateAction(actingFighterId: string): FightAction | null {\n const actor = actingFighterId === 'fighter1' ? this.fight.fighter1 : this.fight.fighter2;\n const target = actingFighterId === 'fighter1' ? this.fight.fighter2 : this.fight.fighter1;\n \n let technique: Technique | null = null;\n \n // In mixed mode, use callbacks for LLM or CPU fighters\n if (this.mixedMode) {\n const gameState = this.getGameState(actingFighterId);\n \n // Check if this fighter should use LLM\n if (actingFighterId === this.llmFighterId && this.llmCallback) {\n // LLM callback - async\n return null; // Will be handled differently - we'll make generateAction async\n }\n \n // Check if this fighter is CPU\n if (actingFighterId !== this.llmFighterId && this.cpuCallback) {\n const techniqueName = this.cpuCallback(actor.name, target.name, gameState);\n technique = this.getTechniqueByName(techniqueName);\n }\n }\n \n // Fall back to AI decision\n if (!technique) {\n technique = this.selectTechnique(actor, target);\n }\n \n if (!technique) return null;\n\n // Calculate success probability\n const success = this.calculateSuccess(actor, target, technique);\n \n // Calculate damage and effects\n const { damage, staminaCost, description, impact } = this.calculateOutcome(\n actor, target, technique, success\n );\n\n return {\n timestamp: Date.now(),\n round: this.fight.currentRound,\n timeRemaining: this.fight.rounds[this.fight.currentRound - 1].timeRemaining,\n actor: actor.name,\n target: target.name,\n type: technique.type,\n technique,\n success,\n damage,\n staminaCost,\n description,\n impact,\n };\n }\n\n // Get technique by name (for LLM/CPU callbacks)\n private getTechniqueByName(name: string): Technique | null {\n const allTechs = [\n ...STRIKING_TECHNIQUES,\n ...GRAPPLING_TECHNIQUES,\n ...SUBMISSION_TECHNIQUES,\n ...GROUND_TECHNIQUES,\n ];\n \n // Exact match\n let tech = allTechs.find(t => t.name.toLowerCase() === name.toLowerCase());\n if (tech) return tech;\n \n // Partial match\n tech = allTechs.find(t => t.name.toLowerCase().includes(name.toLowerCase()));\n return tech || null;\n }\n\n // Generate async action for LLM fighters\n async generateLlmAction(actingFighterId: string): Promise<FightAction | null> {\n if (!this.llmCallback) return null;\n \n const actor = actingFighterId === 'fighter1' ? this.fight.fighter1 : this.fight.fighter2;\n const target = actingFighterId === 'fighter1' ? this.fight.fighter2 : this.fight.fighter1;\n const gameState = this.getGameState(actingFighterId);\n \n try {\n const techniqueName = await this.llmCallback(actor.name, target.name, gameState);\n const technique = this.getTechniqueByName(techniqueName);\n \n if (!technique) {\n console.warn('Invalid technique from LLM:', techniqueName);\n return null;\n }\n \n const success = this.calculateSuccess(actor, target, technique);\n const { damage, staminaCost, description, impact } = this.calculateOutcome(\n actor, target, technique, success\n );\n\n return {\n timestamp: Date.now(),\n round: this.fight.currentRound,\n timeRemaining: this.fight.rounds[this.fight.currentRound - 1].timeRemaining,\n actor: actor.name,\n target: target.name,\n type: technique.type,\n technique,\n success,\n damage,\n staminaCost,\n description,\n impact,\n };\n } catch (error) {\n console.error('LLM callback error:', error);\n // Fallback to AI\n const technique = this.selectTechnique(actor, target);\n if (!technique) return null;\n \n const success = this.calculateSuccess(actor, target, technique);\n const { damage, staminaCost, description, impact } = this.calculateOutcome(\n actor, target, technique, success\n );\n\n return {\n timestamp: Date.now(),\n round: this.fight.currentRound,\n timeRemaining: this.fight.rounds[this.fight.currentRound - 1].timeRemaining,\n actor: actor.name,\n target: target.name,\n type: technique.type,\n technique,\n success,\n damage,\n staminaCost,\n description,\n impact,\n };\n }\n }\n\n private selectTechnique(actor: FighterState, target: FighterState): Technique | null {\n let availableTechs: Technique[] = [];\n\n // Filter techniques by position\n switch (actor.position) {\n case 'standing':\n availableTechs = [\n ...STRIKING_TECHNIQUES.filter(t => t.position.includes('standing')),\n ...GRAPPLING_TECHNIQUES.filter(t => t.position.includes('standing')),\n ];\n break;\n case 'clinch':\n availableTechs = [\n ...STRIKING_TECHNIQUES.filter(t => t.position.includes('clinch')),\n ...GRAPPLING_TECHNIQUES.filter(t => t.position.includes('clinch')),\n { name: 'Break Clinch', type: 'break_clinch', baseDamage: 0, staminaCost: 5, accuracy: 0.80, position: ['clinch'] },\n ];\n break;\n case 'ground_top':\n availableTechs = [\n ...GROUND_TECHNIQUES.filter(t => t.position.includes('ground_top')),\n ...SUBMISSION_TECHNIQUES.filter(t => t.position.includes('ground_top')),\n ];\n break;\n case 'ground_bottom':\n availableTechs = [\n ...GROUND_TECHNIQUES.filter(t => t.position.includes('ground_bottom')),\n ...SUBMISSION_TECHNIQUES.filter(t => t.position.includes('ground_bottom')),\n ];\n break;\n }\n\n if (availableTechs.length === 0) return null;\n\n // AI picks technique based on stats and fight IQ\n const weightedTechs = availableTechs.map(tech => {\n let weight = 1;\n \n // Weight by fighter strengths\n if (tech.type === 'strike') weight *= (actor.striking / 50);\n if (tech.type === 'takedown') weight *= (actor.wrestling / 50);\n if (tech.type === 'submission_attempt') weight *= (actor.submissions / 50);\n if (tech.type === 'ground_pound') weight *= (actor.groundGame / 50);\n \n // Weight by fight IQ (smarter choices)\n if (actor.fightIQ > 70) weight *= (tech.accuracy * 2);\n \n // Contextual decisions\n if (target.currentHealth < 30 && tech.type === 'strike') weight *= 1.5; // Go for finish\n if (actor.currentStamina < 30 && tech.staminaCost > 8) weight *= 0.3; // Conserve energy\n if (target.position === 'ground_bottom' && tech.type === 'ground_pound') weight *= 1.3;\n \n return { tech, weight };\n });\n\n const totalWeight = weightedTechs.reduce((sum, wt) => sum + wt.weight, 0);\n let random = Math.random() * totalWeight;\n \n for (const wt of weightedTechs) {\n random -= wt.weight;\n if (random <= 0) return wt.tech;\n }\n\n return weightedTechs[0]?.tech || null;\n }\n\n private calculateSuccess(actor: FighterState, target: FighterState, tech: Technique): boolean {\n let accuracy = tech.accuracy;\n\n // Modify accuracy based on stats\n if (tech.type === 'strike') {\n accuracy *= (actor.striking / 50);\n accuracy *= (1 - (target.headMovement / 200)); // Head movement reduces accuracy\n }\n if (tech.type === 'takedown') {\n accuracy *= (actor.wrestling / 50);\n accuracy *= (1 - (target.takedownDefense / 150)); // TDD reduces accuracy\n }\n if (tech.type === 'submission_attempt') {\n accuracy *= (actor.submissions / 50);\n accuracy *= (1 - (target.submissionDefense / 150));\n }\n\n // Stamina affects accuracy\n accuracy *= (0.5 + (actor.currentStamina / 200));\n\n // Chin/target health affects strike landing\n if (tech.type === 'strike') {\n accuracy *= (1 + (100 - target.currentHealth) / 300); // Damaged fighters easier to hit\n }\n\n return Math.random() < Math.min(0.95, accuracy);\n }\n\n private calculateOutcome(\n actor: FighterState,\n target: FighterState,\n tech: Technique,\n success: boolean\n ): { damage: number; staminaCost: number; description: string; impact?: 'light' | 'moderate' | 'heavy' | 'devastating' } {\n \n if (!success) {\n const misses = [\n `${actor.name} throws a ${tech.name} but misses!`,\n `${actor.name} attempts a ${tech.name} but ${target.name} defends well!`,\n `${actor.name} goes for a ${tech.name} - no connection!`,\n `${target.name} slips the ${tech.name} from ${actor.name}!`,\n ];\n return {\n damage: 0,\n staminaCost: tech.staminaCost * 0.5,\n description: misses[Math.floor(Math.random() * misses.length)],\n };\n }\n\n let damage = tech.baseDamage;\n let impact: 'light' | 'moderate' | 'heavy' | 'devastating' = 'light';\n\n // Calculate damage multipliers\n if (tech.type === 'strike') {\n damage *= (actor.striking / 50);\n damage *= (0.8 + (actor.punchSpeed / 250)); // Speed adds damage\n \n // Critical hit chance\n if (Math.random() < 0.1) {\n damage *= 1.5;\n impact = 'devastating';\n }\n }\n\n if (tech.type === 'ground_pound') {\n damage *= (actor.groundGame / 50);\n damage *= (0.9 + (actor.striking / 500));\n }\n\n // Target's chin reduces damage\n if (tech.type === 'strike' || tech.type === 'ground_pound') {\n const chinReduction = target.chin / 100;\n damage *= (1.1 - chinReduction);\n \n // Determine impact level\n if (damage > 15) impact = 'devastating';\n else if (damage > 10) impact = 'heavy';\n else if (damage > 5) impact = 'moderate';\n }\n\n // Random variance\n damage *= (0.85 + Math.random() * 0.3);\n\n // Generate description\n const desc = this.generateDescription(actor, target, tech, damage, impact);\n\n return {\n damage: Math.round(damage),\n staminaCost: tech.staminaCost,\n description: desc,\n impact,\n };\n }\n\n private generateDescription(\n actor: FighterState,\n target: FighterState,\n tech: Technique,\n damage: number,\n impact?: string\n ): string {\n const adjectives = impact === 'devastating' ? ['CRUSHING', 'BRUTAL', 'DEVASTATING', 'MASSIVE'] :\n impact === 'heavy' ? ['SOLID', 'HARD', 'CLEAN', 'STIFF'] :\n impact === 'moderate' ? ['nice', 'clean', 'good', 'sharp'] :\n ['light', 'partial', 'glancing'];\n \n const adj = adjectives[Math.floor(Math.random() * adjectives.length)];\n\n if (tech.type === 'submission_attempt') {\n return `${actor.name} locks in a ${tech.name}! ${target.name} is in trouble!`;\n }\n\n if (tech.type === 'takedown') {\n return `${actor.name} scores with a ${tech.name}! Fight goes to the ground!`;\n }\n\n if (damage > 12) {\n return `${adj} ${tech.name} by ${actor.name}! ${target.name} is hurt!`;\n } else if (damage > 6) {\n return `${actor.name} lands a ${adj} ${tech.name}!`;\n } else {\n return `${actor.name} connects with a ${tech.name}.`;\n }\n }\n\n private applyAction(action: FightAction): void {\n const actor = action.actor === this.fight.fighter1.name ? this.fight.fighter1 : this.fight.fighter2;\n const target = action.target === this.fight.fighter1.name ? this.fight.fighter1 : this.fight.fighter2;\n\n // Apply stamina cost\n actor.currentStamina = Math.max(0, actor.currentStamina - action.staminaCost);\n\n // Apply damage\n if (action.damage > 0) {\n target.currentHealth = Math.max(0, target.currentHealth - action.damage);\n \n // Track significant strikes\n if (action.damage >= 5) {\n if (actor === this.fight.fighter1) this.fight.fighter1.significantStrikes++;\n else this.fight.fighter2.significantStrikes++;\n }\n if (actor === this.fight.fighter1) this.fight.fighter1.totalStrikes++;\n else this.fight.fighter2.totalStrikes++;\n }\n\n // Handle position changes\n if (action.type === 'takedown' && action.success) {\n actor.position = 'ground_top';\n target.position = 'ground_bottom';\n actor.takedownsLanded++;\n actor.isGrounded = true;\n target.isGrounded = true;\n }\n\n if (action.type === 'break_clinch' && action.success) {\n actor.position = 'standing';\n target.position = 'standing';\n }\n\n // Handle knockdowns\n if ((action.type === 'strike' || action.type === 'ground_pound') && action.impact === 'devastating' && action.damage > 10) {\n if (Math.random() < 0.3) {\n target.knockdowns++;\n if (target.position === 'standing') {\n target.position = 'ground_bottom';\n actor.position = 'ground_top';\n }\n }\n }\n }\n\n private checkFightEnd(): boolean {\n const f1 = this.fight.fighter1;\n const f2 = this.fight.fighter2;\n\n // KO/TKO check\n if (f1.currentHealth <= 0) {\n this.fight.winner = f2.name;\n this.fight.method = f2.position === 'ground_top' || f2.position === 'ground_bottom' ? 'TKO' : 'KO';\n return true;\n }\n if (f2.currentHealth <= 0) {\n this.fight.winner = f1.name;\n this.fight.method = f1.position === 'ground_top' || f1.position === 'ground_bottom' ? 'TKO' : 'KO';\n return true;\n }\n\n // Cut stoppage\n if (f1.cuts >= 3) {\n this.fight.winner = f2.name;\n this.fight.method = 'TKO';\n return true;\n }\n if (f2.cuts >= 3) {\n this.fight.winner = f1.name;\n this.fight.method = 'TKO';\n return true;\n }\n\n return false;\n }\n\n private checkExhaustion(): boolean {\n // Both fighters too tired to continue\n if (this.fight.fighter1.currentStamina < 5 && this.fight.fighter2.currentStamina < 5) {\n // Pick winner by damage dealt\n const f1Damage = 100 - this.fight.fighter2.currentHealth;\n const f2Damage = 100 - this.fight.fighter1.currentHealth;\n \n if (f1Damage > f2Damage) {\n this.fight.winner = this.fight.fighter1.name;\n } else if (f2Damage > f1Damage) {\n this.fight.winner = this.fight.fighter2.name;\n } else {\n this.fight.winner = undefined;\n this.fight.method = 'DRAW';\n return true;\n }\n this.fight.method = 'TKO';\n return true;\n }\n return false;\n }\n\n private endRound(): void {\n const currentRound = this.fight.rounds[this.fight.currentRound - 1];\n currentRound.isActive = false;\n \n // Recovery between rounds\n this.fight.fighter1.currentHealth = Math.min(100, this.fight.fighter1.currentHealth + this.fight.fighter1.recovery * 0.5);\n this.fight.fighter2.currentHealth = Math.min(100, this.fight.fighter2.currentHealth + this.fight.fighter2.recovery * 0.5);\n this.fight.fighter1.currentStamina = 100;\n this.fight.fighter2.currentStamina = 100;\n \n // Reset positions\n this.fight.fighter1.position = 'standing';\n this.fight.fighter2.position = 'standing';\n\n this.onRoundEnd?.(currentRound);\n\n if (this.fight.currentRound >= TOTAL_ROUNDS) {\n this.endFightByDecision();\n } else {\n this.fight.currentRound++;\n this.fight.rounds.push(this.createRound(this.fight.currentRound));\n this.fight.rounds[this.fight.currentRound - 1].isActive = true;\n }\n }\n\n private endFightByDecision(): void {\n // Score fight based on damage, takedowns, and significant strikes\n const f1Score = (100 - this.fight.fighter2.currentHealth) + \n (this.fight.fighter1.takedownsLanded * 10) + \n (this.fight.fighter1.significantStrikes * 2);\n \n const f2Score = (100 - this.fight.fighter1.currentHealth) + \n (this.fight.fighter2.takedownsLanded * 10) + \n (this.fight.fighter2.significantStrikes * 2);\n\n if (f1Score > f2Score) {\n this.fight.winner = this.fight.fighter1.name;\n } else if (f2Score > f1Score) {\n this.fight.winner = this.fight.fighter2.name;\n } else {\n this.fight.winner = undefined;\n }\n\n this.fight.method = this.fight.winner ? 'DEC' : 'DRAW';\n this.endFight();\n }\n\n private endFight(): void {\n this.pause();\n this.fight.isComplete = true;\n this.fight.endRound = this.fight.currentRound;\n this.fight.endTime = this.fight.rounds[this.fight.currentRound - 1]?.timeRemaining;\n this.onFightEnd?.(this.fight);\n }\n\n // Parse skills.md content into FighterStats\n static parseSkillsMd(content: string): FighterStats {\n const stats = { ...DEFAULT_FIGHTER };\n \n const lines = content.split('\\n');\n for (const line of lines) {\n const [key, value] = line.split(':').map(s => s.trim());\n if (!key || !value) continue;\n \n const numValue = parseFloat(value);\n if (isNaN(numValue)) {\n // String values\n if (key === 'name') stats.name = value;\n if (key === 'nickname') stats.nickname = value;\n continue;\n }\n\n // Map skills.md keys to stats\n switch (key.toLowerCase()) {\n case 'striking':\n case 'boxing':\n stats.striking = numValue;\n break;\n case 'punch_speed':\n case 'handspeed':\n stats.punchSpeed = numValue;\n break;\n case 'kicks':\n case 'kick_power':\n stats.kickPower = numValue;\n break;\n case 'head_movement':\n case 'defense':\n stats.headMovement = numValue;\n break;\n case 'wrestling':\n case 'takedowns':\n stats.wrestling = numValue;\n break;\n case 'takedown_defense':\n case 'tdd':\n stats.takedownDefense = numValue;\n break;\n case 'bjj':\n case 'submissions':\n stats.submissions = numValue;\n break;\n case 'submission_defense':\n stats.submissionDefense = numValue;\n break;\n case 'ground_game':\n case 'grappling':\n stats.groundGame = numValue;\n break;\n case 'cardio':\n case 'stamina':\n stats.cardio = numValue;\n break;\n case 'chin':\n case 'durability':\n stats.chin = numValue;\n break;\n case 'recovery':\n stats.recovery = numValue;\n break;\n case 'iq':\n case 'fight_iq':\n stats.fightIQ = numValue;\n break;\n case 'heart':\n stats.heart = numValue;\n break;\n case 'aggression':\n stats.aggression = numValue;\n break;\n }\n }\n\n return stats;\n }\n}\n\nexport default FightEngine;\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
Binary file
Binary file
@@ -0,0 +1,33 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>FightBook — AI Combat Arena</title>
7
+ <meta name="description" content="Configure your fighter with skills.md. Watch them battle in real-time with authentic MMA techniques. 3-minute rounds, live action, real strategy." />
8
+ <meta name="author" content="FightBook" />
9
+
10
+ <!-- Favicon -->
11
+ <link rel="icon" type="image/png" href="/logo.png" />
12
+
13
+ <!-- Open Graph -->
14
+ <meta property="og:title" content="FightBook — AI Combat Arena" />
15
+ <meta property="og:description" content="Configure your fighter with skills.md. Watch them battle in real-time with authentic MMA techniques." />
16
+ <meta property="og:type" content="website" />
17
+ <meta property="og:image" content="/og-image.png" />
18
+
19
+ <!-- Twitter -->
20
+ <meta name="twitter:card" content="summary_large_image" />
21
+ <meta name="twitter:site" content="@FightBook" />
22
+ <meta name="twitter:image" content="/og-image.png" />
23
+
24
+ <!-- Theme Color -->
25
+ <meta name="theme-color" content="#0a0a0a" />
26
+ <script type="module" crossorigin src="/assets/index-C-_XA-_f.js"></script>
27
+ <link rel="stylesheet" crossorigin href="/assets/index-DDAi7vIJ.css">
28
+ </head>
29
+
30
+ <body>
31
+ <div id="root"></div>
32
+ </body>
33
+ </html>
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ import { FightEngine } from "./engine/FightEngine";
2
+ import {
3
+ POINT_BUDGET,
4
+ POINT_CONSUMING_STATS,
5
+ DEFAULT_SKILLS,
6
+ DEFAULT_PERSONALITY,
7
+ DEFAULT_BACKSTORY,
8
+ ROUND_DURATION,
9
+ TOTAL_ROUNDS,
10
+ STRIKING_TECHNIQUES,
11
+ GRAPPLING_TECHNIQUES,
12
+ SUBMISSION_TECHNIQUES,
13
+ GROUND_TECHNIQUES
14
+ } from "./types/fight";
15
+ import {
16
+ createNewAgent,
17
+ generateFullSkillsMd,
18
+ parseSkillsMd,
19
+ calculateOverallRating,
20
+ detectArchetype,
21
+ calculatePointsSpent,
22
+ calculatePointsRemaining,
23
+ getBudgetStatus,
24
+ canIncreaseStat,
25
+ validateSkillsBudget
26
+ } from "./types/agent";
27
+ const VERSION = "1.0.0";
28
+ export {
29
+ DEFAULT_BACKSTORY,
30
+ DEFAULT_PERSONALITY,
31
+ DEFAULT_SKILLS,
32
+ FightEngine,
33
+ GRAPPLING_TECHNIQUES,
34
+ GROUND_TECHNIQUES,
35
+ POINT_BUDGET,
36
+ POINT_CONSUMING_STATS,
37
+ ROUND_DURATION,
38
+ STRIKING_TECHNIQUES,
39
+ SUBMISSION_TECHNIQUES,
40
+ TOTAL_ROUNDS,
41
+ VERSION,
42
+ calculateOverallRating,
43
+ calculatePointsRemaining,
44
+ calculatePointsSpent,
45
+ canIncreaseStat,
46
+ createNewAgent,
47
+ detectArchetype,
48
+ generateFullSkillsMd,
49
+ getBudgetStatus,
50
+ parseSkillsMd,
51
+ validateSkillsBudget
52
+ };
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["// FightBook - AI Combat Arena\n// NPM Package Entry Point\n\n// Core Engine\nexport { FightEngine } from './engine/FightEngine';\nexport type { GameState } from './engine/FightEngine';\n\n// Types\nexport type {\n SkillsMdConfig,\n AgentPersonality,\n AgentBackstory,\n AgentSocial,\n AgentMetadata,\n CompleteAgent,\n} from './types/agent';\n\nexport type {\n FightState,\n FightAction,\n FighterState,\n FighterStats,\n ActionType,\n Technique,\n RoundState,\n} from './types/fight';\n\n// Constants\nexport {\n POINT_BUDGET,\n POINT_CONSUMING_STATS,\n DEFAULT_SKILLS,\n DEFAULT_PERSONALITY,\n DEFAULT_BACKSTORY,\n ROUND_DURATION,\n TOTAL_ROUNDS,\n STRIKING_TECHNIQUES,\n GRAPPLING_TECHNIQUES,\n SUBMISSION_TECHNIQUES,\n GROUND_TECHNIQUES,\n} from './types/fight';\n\n// Utilities\nexport {\n createNewAgent,\n generateFullSkillsMd,\n parseSkillsMd,\n calculateOverallRating,\n detectArchetype,\n calculatePointsSpent,\n calculatePointsRemaining,\n getBudgetStatus,\n canIncreaseStat,\n validateSkillsBudget,\n} from './types/agent';\n\n// Version\nexport const VERSION = '1.0.0';\n"],
5
+ "mappings": "AAIA,SAAS,mBAAmB;AAwB5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGA,MAAM,UAAU;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
@@ -0,0 +1,14 @@
1
+ User-agent: Googlebot
2
+ Allow: /
3
+
4
+ User-agent: Bingbot
5
+ Allow: /
6
+
7
+ User-agent: Twitterbot
8
+ Allow: /
9
+
10
+ User-agent: facebookexternalhit
11
+ Allow: /
12
+
13
+ User-agent: *
14
+ Allow: /
@@ -0,0 +1,425 @@
1
+ const POINT_BUDGET = {
2
+ TOTAL: 1200,
3
+ // Total points to distribute
4
+ MIN_STAT: 20,
5
+ // Minimum any stat can be
6
+ MAX_STAT: 95,
7
+ // Maximum any stat can be (can't max everything)
8
+ STARTING_BASE: 30
9
+ // Every stat starts at this
10
+ };
11
+ const POINT_CONSUMING_STATS = [
12
+ "striking",
13
+ "punchSpeed",
14
+ "kickPower",
15
+ "headMovement",
16
+ "footwork",
17
+ "combinations",
18
+ "wrestling",
19
+ "takedownDefense",
20
+ "clinchControl",
21
+ "trips",
22
+ "throws",
23
+ "submissions",
24
+ "submissionDefense",
25
+ "groundAndPound",
26
+ "guardPassing",
27
+ "sweeps",
28
+ "topControl",
29
+ "bottomGame",
30
+ "cardio",
31
+ "chin",
32
+ "recovery",
33
+ "strength",
34
+ "flexibility"
35
+ ];
36
+ const DEFAULT_SKILLS = {
37
+ name: "New Agent",
38
+ nickname: "The Prospect",
39
+ striking: 50,
40
+ punchSpeed: 50,
41
+ kickPower: 50,
42
+ headMovement: 50,
43
+ footwork: 50,
44
+ combinations: 50,
45
+ wrestling: 50,
46
+ takedownDefense: 50,
47
+ clinchControl: 50,
48
+ trips: 50,
49
+ throws: 50,
50
+ submissions: 50,
51
+ submissionDefense: 50,
52
+ groundAndPound: 50,
53
+ guardPassing: 50,
54
+ sweeps: 50,
55
+ topControl: 50,
56
+ bottomGame: 50,
57
+ cardio: 50,
58
+ chin: 50,
59
+ recovery: 50,
60
+ strength: 50,
61
+ flexibility: 50,
62
+ aggression: 0.5,
63
+ fightIQ: 50,
64
+ heart: 50,
65
+ adaptability: 50,
66
+ ringGeneralship: 50,
67
+ preferredRange: "distance",
68
+ finishingInstinct: 50,
69
+ defensiveTendency: 50
70
+ };
71
+ const DEFAULT_PERSONALITY = {
72
+ archetype: "balanced",
73
+ attitude: "intense",
74
+ preFightQuote: "I'm here to fight. Let's go.",
75
+ winQuote: "Victory belongs to the most prepared.",
76
+ lossQuote: "I'll be back stronger.",
77
+ fightingPhilosophy: "Adapt and overcome."
78
+ };
79
+ const DEFAULT_BACKSTORY = {
80
+ origin: "Unknown",
81
+ trainingCamp: "FightBook Academy",
82
+ signatureMove: "The Haymaker",
83
+ rivalries: [],
84
+ achievements: ["Debut Fight"]
85
+ };
86
+ const DEFAULT_SOCIAL = {
87
+ agentName: "agent007"
88
+ };
89
+ function createNewAgent(name) {
90
+ const now = Date.now();
91
+ return {
92
+ metadata: {
93
+ id: `agent_${now}_${Math.random().toString(36).substring(7)}`,
94
+ name,
95
+ createdAt: now,
96
+ updatedAt: now,
97
+ version: 1,
98
+ totalFights: 0,
99
+ wins: 0,
100
+ losses: 0,
101
+ draws: 0,
102
+ kos: 0,
103
+ submissions: 0,
104
+ currentStreak: 0,
105
+ bestStreak: 0,
106
+ ranking: 1e3,
107
+ earnings: 0,
108
+ xp: 0,
109
+ level: 1
110
+ },
111
+ skills: { ...DEFAULT_SKILLS, name, nickname: "The Prospect" },
112
+ personality: { ...DEFAULT_PERSONALITY },
113
+ backstory: { ...DEFAULT_BACKSTORY },
114
+ social: { ...DEFAULT_SOCIAL, agentName: name.toLowerCase().replace(/\s+/g, "_") }
115
+ };
116
+ }
117
+ function generateFullSkillsMd(agent) {
118
+ const { skills, personality, backstory } = agent;
119
+ return `# ${skills.name} - FightBook Agent Configuration
120
+ # Version: ${agent.metadata.version} | Level: ${agent.metadata.level}
121
+
122
+ ## IDENTITY
123
+ name: ${skills.name}
124
+ nickname: ${skills.nickname}
125
+ archetype: ${personality.archetype}
126
+ attitude: ${personality.attitude}
127
+
128
+ ## STRIKING
129
+ striking: ${skills.striking}
130
+ punch_speed: ${skills.punchSpeed}
131
+ kick_power: ${skills.kickPower}
132
+ head_movement: ${skills.headMovement}
133
+ footwork: ${skills.footwork}
134
+ combinations: ${skills.combinations}
135
+
136
+ ## GRAPPLING
137
+ wrestling: ${skills.wrestling}
138
+ takedown_defense: ${skills.takedownDefense}
139
+ clinch_control: ${skills.clinchControl}
140
+ trips: ${skills.trips}
141
+ throws: ${skills.throws}
142
+
143
+ ## GROUND GAME
144
+ submissions: ${skills.submissions}
145
+ submission_defense: ${skills.submissionDefense}
146
+ ground_and_pound: ${skills.groundAndPound}
147
+ guard_passing: ${skills.guardPassing}
148
+ sweeps: ${skills.sweeps}
149
+ top_control: ${skills.topControl}
150
+ bottom_game: ${skills.bottomGame}
151
+
152
+ ## PHYSICAL
153
+ cardio: ${skills.cardio}
154
+ chin: ${skills.chin}
155
+ recovery: ${skills.recovery}
156
+ strength: ${skills.strength}
157
+ flexibility: ${skills.flexibility}
158
+
159
+ ## MENTAL
160
+ aggression: ${skills.aggression}
161
+ fight_iq: ${skills.fightIQ}
162
+ heart: ${skills.heart}
163
+ adaptability: ${skills.adaptability}
164
+ ring_generalship: ${skills.ringGeneralship}
165
+
166
+ ## STRATEGY
167
+ preferred_range: ${skills.preferredRange}
168
+ finishing_instinct: ${skills.finishingInstinct}
169
+ defensive_tendency: ${skills.defensiveTendency}
170
+
171
+ ## PERSONALITY
172
+ pre_fight_quote: "${personality.preFightQuote}"
173
+ win_quote: "${personality.winQuote}"
174
+ loss_quote: "${personality.lossQuote}"
175
+ philosophy: "${personality.fightingPhilosophy}"
176
+
177
+ ## BACKSTORY
178
+ origin: ${backstory.origin}
179
+ training_camp: ${backstory.trainingCamp}
180
+ signature_move: ${backstory.signatureMove}
181
+ `;
182
+ }
183
+ function parseSkillsMd(content) {
184
+ const skills = {};
185
+ const lines = content.split("\n");
186
+ for (const line of lines) {
187
+ const trimmed = line.trim();
188
+ if (!trimmed || trimmed.startsWith("#")) continue;
189
+ const [key, ...valueParts] = trimmed.split(":");
190
+ if (!key) continue;
191
+ const value = valueParts.join(":").trim();
192
+ if (!value) continue;
193
+ const numValue = parseFloat(value);
194
+ const isNumber = !isNaN(numValue);
195
+ switch (key.toLowerCase()) {
196
+ case "name":
197
+ skills.name = value;
198
+ break;
199
+ case "nickname":
200
+ skills.nickname = value;
201
+ break;
202
+ case "striking":
203
+ if (isNumber) skills.striking = numValue;
204
+ break;
205
+ case "punch_speed":
206
+ if (isNumber) skills.punchSpeed = numValue;
207
+ break;
208
+ case "kick_power":
209
+ if (isNumber) skills.kickPower = numValue;
210
+ break;
211
+ case "head_movement":
212
+ if (isNumber) skills.headMovement = numValue;
213
+ break;
214
+ case "footwork":
215
+ if (isNumber) skills.footwork = numValue;
216
+ break;
217
+ case "combinations":
218
+ if (isNumber) skills.combinations = numValue;
219
+ break;
220
+ case "wrestling":
221
+ if (isNumber) skills.wrestling = numValue;
222
+ break;
223
+ case "takedown_defense":
224
+ if (isNumber) skills.takedownDefense = numValue;
225
+ break;
226
+ case "clinch_control":
227
+ if (isNumber) skills.clinchControl = numValue;
228
+ break;
229
+ case "trips":
230
+ if (isNumber) skills.trips = numValue;
231
+ break;
232
+ case "throws":
233
+ if (isNumber) skills.throws = numValue;
234
+ break;
235
+ case "submissions":
236
+ if (isNumber) skills.submissions = numValue;
237
+ break;
238
+ case "submission_defense":
239
+ if (isNumber) skills.submissionDefense = numValue;
240
+ break;
241
+ case "ground_and_pound":
242
+ if (isNumber) skills.groundAndPound = numValue;
243
+ break;
244
+ case "guard_passing":
245
+ if (isNumber) skills.guardPassing = numValue;
246
+ break;
247
+ case "sweeps":
248
+ if (isNumber) skills.sweeps = numValue;
249
+ break;
250
+ case "top_control":
251
+ if (isNumber) skills.topControl = numValue;
252
+ break;
253
+ case "bottom_game":
254
+ if (isNumber) skills.bottomGame = numValue;
255
+ break;
256
+ case "cardio":
257
+ if (isNumber) skills.cardio = numValue;
258
+ break;
259
+ case "chin":
260
+ if (isNumber) skills.chin = numValue;
261
+ break;
262
+ case "recovery":
263
+ if (isNumber) skills.recovery = numValue;
264
+ break;
265
+ case "strength":
266
+ if (isNumber) skills.strength = numValue;
267
+ break;
268
+ case "flexibility":
269
+ if (isNumber) skills.flexibility = numValue;
270
+ break;
271
+ case "aggression":
272
+ if (isNumber) skills.aggression = numValue;
273
+ break;
274
+ case "fight_iq":
275
+ if (isNumber) skills.fightIQ = numValue;
276
+ break;
277
+ case "heart":
278
+ if (isNumber) skills.heart = numValue;
279
+ break;
280
+ case "adaptability":
281
+ if (isNumber) skills.adaptability = numValue;
282
+ break;
283
+ case "ring_generalship":
284
+ if (isNumber) skills.ringGeneralship = numValue;
285
+ break;
286
+ case "preferred_range":
287
+ if (["distance", "clinch", "ground"].includes(value)) {
288
+ skills.preferredRange = value;
289
+ }
290
+ break;
291
+ case "finishing_instinct":
292
+ if (isNumber) skills.finishingInstinct = numValue;
293
+ break;
294
+ case "defensive_tendency":
295
+ if (isNumber) skills.defensiveTendency = numValue;
296
+ break;
297
+ }
298
+ }
299
+ return skills;
300
+ }
301
+ function calculateOverallRating(skills) {
302
+ const weights = {
303
+ striking: 1,
304
+ punchSpeed: 0.8,
305
+ kickPower: 0.7,
306
+ headMovement: 0.9,
307
+ footwork: 0.8,
308
+ combinations: 0.7,
309
+ wrestling: 1,
310
+ takedownDefense: 0.9,
311
+ clinchControl: 0.7,
312
+ trips: 0.5,
313
+ throws: 0.5,
314
+ submissions: 0.9,
315
+ submissionDefense: 0.8,
316
+ groundAndPound: 0.7,
317
+ guardPassing: 0.7,
318
+ sweeps: 0.6,
319
+ topControl: 0.8,
320
+ bottomGame: 0.7,
321
+ cardio: 1,
322
+ chin: 1,
323
+ recovery: 0.8,
324
+ strength: 0.7,
325
+ flexibility: 0.6,
326
+ fightIQ: 0.9,
327
+ heart: 0.8,
328
+ adaptability: 0.7,
329
+ ringGeneralship: 0.8
330
+ };
331
+ let total = 0;
332
+ let weightSum = 0;
333
+ for (const [key, weight] of Object.entries(weights)) {
334
+ const value = skills[key] || 0;
335
+ total += value * weight;
336
+ weightSum += weight;
337
+ }
338
+ total += skills.aggression * 50;
339
+ weightSum += 0.5;
340
+ return Math.round(total / weightSum);
341
+ }
342
+ function detectArchetype(skills) {
343
+ const striking = (skills.striking + skills.punchSpeed + skills.kickPower) / 3;
344
+ const grappling = (skills.wrestling + skills.submissions + skills.topControl + skills.bottomGame) / 4;
345
+ const defense = (skills.headMovement + skills.takedownDefense + skills.submissionDefense) / 3;
346
+ if (striking > 70 && grappling < 50) return "striker";
347
+ if (grappling > 70 && striking < 50) return "grappler";
348
+ if (defense > 65 && skills.aggression < 0.4) return "counter";
349
+ if (skills.aggression > 0.8) return "pressure";
350
+ if (striking > 65 && grappling > 65) return "balanced";
351
+ return "wildcard";
352
+ }
353
+ function calculatePointsSpent(skills) {
354
+ return POINT_CONSUMING_STATS.reduce((total, stat) => {
355
+ const value = skills[stat] || 0;
356
+ return total + Math.max(0, value - POINT_BUDGET.STARTING_BASE);
357
+ }, 0);
358
+ }
359
+ function calculatePointsRemaining(skills) {
360
+ return POINT_BUDGET.TOTAL - calculatePointsSpent(skills);
361
+ }
362
+ function canIncreaseStat(skills, stat) {
363
+ if (!POINT_CONSUMING_STATS.includes(stat)) return true;
364
+ const currentValue = skills[stat] || 0;
365
+ if (currentValue >= POINT_BUDGET.MAX_STAT) return false;
366
+ return calculatePointsRemaining(skills) > 0;
367
+ }
368
+ function getBudgetStatus(skills) {
369
+ const spent = calculatePointsSpent(skills);
370
+ const remaining = POINT_BUDGET.TOTAL - spent;
371
+ const percentUsed = spent / POINT_BUDGET.TOTAL * 100;
372
+ let color = "text-green-500";
373
+ let status = "Balanced";
374
+ if (percentUsed > 90) {
375
+ color = "text-red-500";
376
+ status = "Maxed";
377
+ } else if (percentUsed > 75) {
378
+ color = "text-orange-500";
379
+ status = "High";
380
+ } else if (percentUsed > 50) {
381
+ color = "text-yellow-500";
382
+ status = "Medium";
383
+ }
384
+ return { spent, remaining, percentUsed, color, status };
385
+ }
386
+ function validateSkillsBudget(skills) {
387
+ const errors = [];
388
+ const warnings = [];
389
+ const spent = calculatePointsSpent(skills);
390
+ if (spent > POINT_BUDGET.TOTAL) {
391
+ errors.push(`Over budget by ${spent - POINT_BUDGET.TOTAL} points`);
392
+ }
393
+ POINT_CONSUMING_STATS.forEach((stat) => {
394
+ const value = skills[stat] || 0;
395
+ if (value < POINT_BUDGET.MIN_STAT) {
396
+ errors.push(`${stat} is below minimum ${POINT_BUDGET.MIN_STAT}`);
397
+ }
398
+ if (value > POINT_BUDGET.MAX_STAT) {
399
+ errors.push(`${stat} exceeds maximum ${POINT_BUDGET.MAX_STAT}`);
400
+ }
401
+ });
402
+ if (spent < POINT_BUDGET.TOTAL * 0.5) {
403
+ warnings.push("You have unspent points - use them or lose them!");
404
+ }
405
+ return { valid: errors.length === 0, errors, warnings };
406
+ }
407
+ export {
408
+ DEFAULT_BACKSTORY,
409
+ DEFAULT_PERSONALITY,
410
+ DEFAULT_SKILLS,
411
+ DEFAULT_SOCIAL,
412
+ POINT_BUDGET,
413
+ POINT_CONSUMING_STATS,
414
+ calculateOverallRating,
415
+ calculatePointsRemaining,
416
+ calculatePointsSpent,
417
+ canIncreaseStat,
418
+ createNewAgent,
419
+ detectArchetype,
420
+ generateFullSkillsMd,
421
+ getBudgetStatus,
422
+ parseSkillsMd,
423
+ validateSkillsBudget
424
+ };
425
+ //# sourceMappingURL=agent.js.map