Fraser commited on
Commit
ac7c05c
Β·
1 Parent(s): 782c122

complex piclet

Browse files
src/lib/components/PicletGenerator/PicletGenerator.svelte CHANGED
@@ -455,8 +455,8 @@ Create a concise visual description (1-3 sentences, max 100 words). Focus only o
455
  const rarityMatch = state.picletConcept.match(/# Object Rarity\s*\n([\s\S]*?)(?=^#)/m);
456
  const objectRarity = rarityMatch ? rarityMatch[1].trim().toLowerCase() : 'common';
457
 
458
- // Create stats prompt using both the detailed object description and monster concept
459
- const statsPrompt = `Based on this detailed object description and monster concept, generate a JSON object with battle stats and abilities:
460
 
461
  ORIGINAL OBJECT DESCRIPTION:
462
  "${state.imageCaption}"
@@ -466,54 +466,194 @@ MONSTER CONCEPT:
466
 
467
  The object rarity has been assessed as: ${objectRarity}
468
 
469
- Use this rarity level to determine appropriate stats:
 
 
 
 
470
 
471
- Next, determine the monster's type based on its concept and appearance. Choose the most appropriate type from these options:
472
- β€’ BEAST: Vertebrate wildlife β€” mammals, birds, reptiles. Raw physicality, instincts, region-based variants.
473
- β€’ BUG: Arthropods β€” butterflies, beetles, mantises. Agile swarms, precision strikes, metamorph evolutions.
474
- β€’ AQUATIC: Life that swims, dives, sloshes, seeps β€” fish, octopus, sentient puddles. Tides, mist, pressure.
475
- β€’ FLORA: Plants and fungi β€” blooming or decaying. Growth, spores, vines, seasonal shifts.
476
- β€’ MINERAL: Stones, crystals, metals β€” earth's depths. Durability, reflective armor, seismic shocks.
477
- β€’ SPACE: Stars, moon, cosmic objects β€” not of this world. Celestial energy and cosmic forces.
478
- β€’ MACHINA: Engineered devices β€” gadgets to machinery. Gears, circuits, drones, power surges.
479
- β€’ STRUCTURE: Buildings, bridges, monuments β€” architectural titans. Fortification, terrain shaping.
480
- β€’ CULTURE: Art, fashion, toys, symbols β€” creative expressions. Buffs, debuffs, illusion, stories.
481
- β€’ CUISINE: Dishes, drinks, culinary art β€” flavors and aromas. Temperature, restorative, spicy offense.
 
482
 
483
- The output should be formatted as a JSON instance that conforms to the JSON schema below.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
 
485
  \`\`\`json
486
  {
 
487
  "properties": {
488
  "name": {"type": "string", "description": "Creative name for the monster that hints at the original object"},
489
- "rarity": {"type": "string", "enum": ["common", "uncommon", "rare", "legendary"], "description": "Rarity of the original object based on real-world availability and value"},
490
- "picletType": {"type": "string", "enum": ["beast", "bug", "aquatic", "flora", "mineral", "space", "machina", "structure", "culture", "cuisine"], "description": "The type that best matches this monster's concept, appearance, and nature"},
491
- "height": {"type": "number", "minimum": 0.1, "maximum": 50.0, "description": "Height of the piclet in meters (e.g., 1.2, 0.5, 10.0)"},
492
- "weight": {"type": "number", "minimum": 0.1, "maximum": 10000.0, "description": "Weight of the piclet in kilograms (e.g., 25.4, 150.0, 0.8)"},
493
- "HP": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Health/vitality stat (0=fragile, 100=incredibly tanky)"},
494
- "defence": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Defensive/armor stat (0=paper thin, 100=impenetrable fortress)"},
495
- "attack": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Physical attack power (0=harmless, 100=devastating force)"},
496
- "speed": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Movement and reaction speed (0=immobile, 100=lightning fast)"},
497
- "monsterLore": {"type": "string", "description": "Write a background story for this monster including its personality, habitat, behavior, and lore (2-3 sentences)"},
498
- "specialPassiveTraitDescription": {"type": "string", "description": "Describe a passive ability that gives this monster a unique advantage in battle"},
499
- "attackActionName": {"type": "string", "description": "Name of the monster's primary damage-dealing attack (e.g., 'Flame Burst', 'Toxic Bite')"},
500
- "attackActionDescription": {"type": "string", "description": "Describe how this attack damages the opponent and any special effects"},
501
- "buffActionName": {"type": "string", "description": "Name of the monster's self-enhancement ability (e.g., 'Iron Defense', 'Speed Boost')"},
502
- "buffActionDescription": {"type": "string", "description": "Describe which stats are boosted and how this improves the monster's battle performance"},
503
- "debuffActionName": {"type": "string", "description": "Name of the monster's enemy-weakening ability (e.g., 'Intimidate', 'Slow Poison')"},
504
- "debuffActionDescription": {"type": "string", "description": "Describe which enemy stats are lowered and how this weakens the opponent"},
505
- "specialActionName": {"type": "string", "description": "Name of the monster's ultimate move (one use per battle)"},
506
- "specialActionDescription": {"type": "string", "description": "Describe this powerful finishing move and its dramatic effects in battle"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  },
508
- "required": ["name", "rarity", "picletType", "height", "weight", "HP", "defence", "attack", "speed", "monsterLore", "specialPassiveTraitDescription", "attackActionName", "attackActionDescription", "buffActionName", "buffActionDescription", "debuffActionName", "debuffActionDescription", "specialActionName", "specialActionDescription"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  }
510
  \`\`\`
511
 
512
- Base the HP, defence, attack, and speed stats on the rarity level:
513
- - common: stats should be 10-40
514
- - uncommon: stats should be 30-60
515
- - rare: stats should be 50-80
516
- - legendary: stats should be 70-100
 
 
 
 
 
 
 
517
 
518
  Write your response within \`\`\`json\`\`\``;
519
 
@@ -605,73 +745,42 @@ Write your response within \`\`\`json\`\`\``;
605
 
606
  const parsedStats = JSON.parse(cleanJson.trim());
607
 
608
- // Remove any extra fields not in our schema
609
- const allowedFields = ['name', 'rarity', 'picletType', 'height', 'weight', 'HP', 'defence', 'attack', 'speed',
610
- 'monsterLore', 'specialPassiveTraitDescription', 'attackActionName', 'attackActionDescription',
611
- 'buffActionName', 'buffActionDescription', 'debuffActionName', 'debuffActionDescription',
612
- 'specialActionName', 'specialActionDescription', 'boostActionName', 'boostActionDescription',
613
- 'disparageActionName', 'disparageActionDescription'];
614
 
615
- for (const key in parsedStats) {
616
- if (!allowedFields.includes(key)) {
617
- delete parsedStats[key];
618
- }
619
  }
620
 
621
- // Map rarity to tier
622
- if (parsedStats.rarity) {
623
- const tierMap: { [key: string]: 'low' | 'medium' | 'high' | 'legendary' } = {
624
- 'common': 'low',
625
- 'uncommon': 'medium',
626
- 'rare': 'high',
627
- 'legendary': 'legendary'
628
- };
629
- tier = tierMap[parsedStats.rarity.toLowerCase()] || 'medium';
630
- }
631
-
632
- // Add the description and tier that we extracted/mapped
633
- // Use the name from the structured concept, or fall back to JSON response
634
- if (!parsedStats.name) {
635
- parsedStats.name = monsterName;
636
  }
637
- parsedStats.description = parsedStats.monsterLore || 'A mysterious creature with unknown origins.';
638
- parsedStats.tier = tier;
639
 
640
- // Ensure numeric fields are actually numbers
641
- const numericFields = ['HP', 'defence', 'attack', 'speed'];
642
-
643
- for (const field of numericFields) {
644
- if (parsedStats[field] !== undefined) {
645
- // Convert string numbers to actual numbers
646
- parsedStats[field] = parseInt(parsedStats[field]);
647
-
648
- // Clamp to 0-100 range
649
- parsedStats[field] = Math.max(0, Math.min(100, parsedStats[field]));
650
  }
651
  }
652
 
653
- // Map field names from schema to interface
654
- if (parsedStats.specialPassiveTraitDescription) {
655
- parsedStats.specialPassiveTrait = parsedStats.specialPassiveTraitDescription;
656
- delete parsedStats.specialPassiveTraitDescription;
657
- }
658
 
659
- // Handle potential old field names from LLM
660
- if (parsedStats.boostActionName) {
661
- parsedStats.buffActionName = parsedStats.boostActionName;
662
- delete parsedStats.boostActionName;
663
- }
664
- if (parsedStats.boostActionDescription) {
665
- parsedStats.buffActionDescription = parsedStats.boostActionDescription;
666
- delete parsedStats.boostActionDescription;
667
- }
668
- if (parsedStats.disparageActionName) {
669
- parsedStats.debuffActionName = parsedStats.disparageActionName;
670
- delete parsedStats.disparageActionName;
671
  }
672
- if (parsedStats.disparageActionDescription) {
673
- parsedStats.debuffActionDescription = parsedStats.disparageActionDescription;
674
- delete parsedStats.disparageActionDescription;
 
 
 
 
 
 
675
  }
676
 
677
  const stats: PicletStats = parsedStats;
 
455
  const rarityMatch = state.picletConcept.match(/# Object Rarity\s*\n([\s\S]*?)(?=^#)/m);
456
  const objectRarity = rarityMatch ? rarityMatch[1].trim().toLowerCase() : 'common';
457
 
458
+ // Create comprehensive battle-ready monster prompt
459
+ const statsPrompt = `Based on this detailed object description and monster concept, create a complete battle-ready monster for the Pictuary Battle System:
460
 
461
  ORIGINAL OBJECT DESCRIPTION:
462
  "${state.imageCaption}"
 
466
 
467
  The object rarity has been assessed as: ${objectRarity}
468
 
469
+ ## BATTLE SYSTEM OVERVIEW
470
+ This monster will be used in a turn-based battle system with composable effects. You must create:
471
+ 1. **Base Stats**: Core combat statistics
472
+ 2. **Special Ability**: Passive trait with triggers and effects
473
+ 3. **Movepool**: 4 battle moves with complex effect combinations
474
 
475
+ ## TYPE SYSTEM
476
+ Choose the primary type (and optional secondary type) based on the object:
477
+ β€’ **beast**: Vertebrate wildlife β€” mammals, birds, reptiles. Raw physicality, instincts
478
+ β€’ **bug**: Arthropods β€” butterflies, beetles, mantises. Agile swarms, precision strikes
479
+ β€’ **aquatic**: Life that swims, dives, sloshes β€” fish, octopus, sentient puddles. Tides, pressure
480
+ β€’ **flora**: Plants and fungi β€” blooming or decaying. Growth, spores, vines, seasonal shifts
481
+ β€’ **mineral**: Stones, crystals, metals β€” earth's depths. Durability, reflective armor, seismic shocks
482
+ β€’ **space**: Stars, moon, cosmic objects β€” not of this world. Celestial energy, gravitational effects
483
+ β€’ **machina**: Engineered devices β€” gadgets to machinery. Gears, circuits, drones, power surges
484
+ β€’ **structure**: Buildings, bridges, monuments β€” architectural titans. Fortification, terrain shaping
485
+ β€’ **culture**: Art, fashion, toys, symbols β€” creative expressions. Buffs, debuffs, illusion, stories
486
+ β€’ **cuisine**: Dishes, drinks, culinary art β€” flavors and aromas. Temperature, restorative effects
487
 
488
+ ## EFFECT SYSTEM
489
+ All abilities and moves use these **atomic building blocks**:
490
+
491
+ ### **Effect Types:**
492
+ 1. **damage**: Deal damage (weak/normal/strong/extreme) with formulas (standard/recoil/drain/fixed/percentage)
493
+ 2. **modifyStats**: Change stats (increase/decrease/greatly_increase/greatly_decrease) for hp/attack/defense/speed/accuracy
494
+ 3. **applyStatus**: Apply status effects (burn/freeze/paralyze/poison/sleep/confuse) with chance percentage
495
+ 4. **heal**: Restore HP (small/medium/large/full) or by percentage/fixed amounts
496
+ 5. **manipulatePP**: Drain, restore, or disable PP from moves
497
+ 6. **fieldEffect**: Battlefield modifications (reflect, lightScreen, spikes, etc.)
498
+ 7. **counter**: Reflect damage based on attack type (physical/special/any)
499
+ 8. **priority**: Modify move priority (-5 to +5)
500
+ 9. **removeStatus**: Cure specific status conditions
501
+ 10. **mechanicOverride**: Modify core game mechanics (immunity, type changes, etc.)
502
+
503
+ ### **Targets:**
504
+ - **self**: The move user
505
+ - **opponent**: The target opponent
506
+ - **all**: All combatants
507
+ - **allies**: Allied creatures (team battles)
508
+ - **field**: Entire battlefield
509
+
510
+ ### **Conditions:**
511
+ - **always**: Effect always applies
512
+ - **onHit**: Only if move hits successfully
513
+ - **afterUse**: After move execution regardless of hit/miss
514
+ - **onCritical**: Only on critical hits
515
+ - **ifLowHp**: If user's HP < 25%
516
+ - **ifHighHp**: If user's HP > 75%
517
+
518
+ ### **Move Flags:**
519
+ Moves can have flags affecting interactions:
520
+ - **contact**: Makes physical contact (triggers contact abilities)
521
+ - **explosive**: Explosive move (affected by explosion-related abilities)
522
+ - **draining**: Drains HP from target
523
+ - **priority**: Natural priority move (+1 to +5)
524
+ - **sacrifice**: Involves self-sacrifice or major cost
525
+ - **reckless**: High power with drawbacks
526
+
527
+ ## SPECIAL ABILITY TRIGGERS
528
+ Special abilities activate on specific events:
529
+ - **onSwitchIn**: When entering battle
530
+ - **onDamageTaken**: When this monster takes damage
531
+ - **onContactDamage**: When hit by a contact move
532
+ - **endOfTurn**: At the end of each turn
533
+ - **onLowHP**: When HP drops below 25%
534
+ - **onStatusInflicted**: When a status is applied
535
+
536
+ ## BALANCING GUIDELINES
537
+ **Stat Ranges by Rarity:**
538
+ - **common**: 45-80 total stats, individual stats 10-25
539
+ - **uncommon**: 80-120 total stats, individual stats 15-35
540
+ - **rare**: 120-160 total stats, individual stats 25-45
541
+ - **legendary**: 160-200+ total stats, individual stats 35-50
542
+
543
+ **Design Philosophy:**
544
+ - **Risk-Reward**: Powerful moves must have meaningful drawbacks
545
+ - **Type Synergy**: Moves should match the monster's type and concept
546
+ - **Strategic Depth**: Abilities should create interesting decision points
547
+ - **No Strictly Better**: Every powerful effect has a cost or condition
548
+
549
+ The output should be formatted as a JSON instance that conforms to the schema below.
550
 
551
  \`\`\`json
552
  {
553
+ "type": "object",
554
  "properties": {
555
  "name": {"type": "string", "description": "Creative name for the monster that hints at the original object"},
556
+ "description": {"type": "string", "description": "Flavor text describing the monster (2-3 sentences)"},
557
+ "tier": {"type": "string", "enum": ["low", "medium", "high", "legendary"], "description": "Power tier based on rarity: common=low, uncommon=medium, rare=high, legendary=legendary"},
558
+ "primaryType": {"type": "string", "enum": ["beast", "bug", "aquatic", "flora", "mineral", "space", "machina", "structure", "culture", "cuisine"], "description": "Primary type based on object characteristics"},
559
+ "secondaryType": {"type": ["string", "null"], "enum": ["beast", "bug", "aquatic", "flora", "mineral", "space", "machina", "structure", "culture", "cuisine", null], "description": "Optional secondary type for dual-type monsters"},
560
+ "baseStats": {
561
+ "type": "object",
562
+ "properties": {
563
+ "hp": {"type": "integer", "minimum": 10, "maximum": 50, "description": "Hit points"},
564
+ "attack": {"type": "integer", "minimum": 10, "maximum": 50, "description": "Attack power"},
565
+ "defense": {"type": "integer", "minimum": 10, "maximum": 50, "description": "Defensive capability"},
566
+ "speed": {"type": "integer", "minimum": 10, "maximum": 50, "description": "Speed and agility"}
567
+ },
568
+ "required": ["hp", "attack", "defense", "speed"],
569
+ "additionalProperties": false
570
+ },
571
+ "nature": {"type": "string", "description": "Personality trait affecting behavior (e.g., 'bold', 'timid', 'hasty')"},
572
+ "specialAbility": {
573
+ "type": "object",
574
+ "properties": {
575
+ "name": {"type": "string", "description": "Name of the special ability"},
576
+ "description": {"type": "string", "description": "Description of what the ability does"},
577
+ "effects": {
578
+ "type": "array",
579
+ "items": {"$ref": "#/definitions/Effect"},
580
+ "description": "Passive effects that are always active"
581
+ },
582
+ "triggers": {
583
+ "type": "array",
584
+ "items": {"$ref": "#/definitions/Trigger"},
585
+ "description": "Effects that trigger on specific events"
586
+ }
587
+ },
588
+ "required": ["name", "description"],
589
+ "additionalProperties": false
590
+ },
591
+ "movepool": {
592
+ "type": "array",
593
+ "items": {"$ref": "#/definitions/Move"},
594
+ "minItems": 4,
595
+ "maxItems": 4,
596
+ "description": "Exactly 4 battle moves"
597
+ }
598
  },
599
+ "required": ["name", "description", "tier", "primaryType", "baseStats", "nature", "specialAbility", "movepool"],
600
+ "additionalProperties": false,
601
+ "definitions": {
602
+ "Effect": {
603
+ "type": "object",
604
+ "properties": {
605
+ "type": {"type": "string", "enum": ["damage", "modifyStats", "applyStatus", "heal", "manipulatePP", "fieldEffect", "counter", "priority", "removeStatus", "mechanicOverride"]},
606
+ "target": {"type": "string", "enum": ["self", "opponent", "allies", "all", "attacker", "field", "playerSide", "opponentSide"]},
607
+ "condition": {"type": "string", "enum": ["always", "onHit", "afterUse", "onCritical", "ifLowHp", "ifHighHp", "thisTurn", "nextTurn", "restOfBattle"]}
608
+ },
609
+ "required": ["type"],
610
+ "allOf": [
611
+ {"if": {"properties": {"type": {"const": "damage"}}}, "then": {"properties": {"amount": {"enum": ["weak", "normal", "strong", "extreme"]}, "formula": {"enum": ["standard", "recoil", "drain", "fixed", "percentage"]}, "value": {"type": "number"}}}},
612
+ {"if": {"properties": {"type": {"const": "modifyStats"}}}, "then": {"properties": {"stats": {"type": "object", "properties": {"hp": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}, "attack": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}, "defense": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}, "speed": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}, "accuracy": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}}}}}},
613
+ {"if": {"properties": {"type": {"const": "applyStatus"}}}, "then": {"properties": {"status": {"enum": ["burn", "freeze", "paralyze", "poison", "sleep", "confuse"]}, "chance": {"type": "number", "minimum": 1, "maximum": 100}}}},
614
+ {"if": {"properties": {"type": {"const": "heal"}}}, "then": {"properties": {"amount": {"enum": ["small", "medium", "large", "full"]}, "formula": {"enum": ["percentage", "fixed"]}, "value": {"type": "number"}}}}
615
+ ]
616
+ },
617
+ "Trigger": {
618
+ "type": "object",
619
+ "properties": {
620
+ "event": {"type": "string", "enum": ["onSwitchIn", "onDamageTaken", "onContactDamage", "endOfTurn", "onLowHP", "onStatusInflicted", "beforeMoveUse", "afterMoveUse"]},
621
+ "condition": {"type": "string", "enum": ["always", "ifLowHp", "ifHighHp", "ifStatus:burn", "ifStatus:freeze", "ifStatus:paralyze", "ifStatus:poison", "ifStatus:sleep", "ifStatus:confuse"]},
622
+ "effects": {"type": "array", "items": {"$ref": "#/definitions/Effect"}, "minItems": 1}
623
+ },
624
+ "required": ["event", "effects"]
625
+ },
626
+ "Move": {
627
+ "type": "object",
628
+ "properties": {
629
+ "name": {"type": "string", "description": "Name of the move"},
630
+ "type": {"type": "string", "enum": ["beast", "bug", "aquatic", "flora", "mineral", "space", "machina", "structure", "culture", "cuisine", "normal"], "description": "Move type for STAB and effectiveness"},
631
+ "power": {"type": "integer", "minimum": 0, "maximum": 250, "description": "Base power (0 for status moves)"},
632
+ "accuracy": {"type": "integer", "minimum": 30, "maximum": 100, "description": "Hit chance percentage"},
633
+ "pp": {"type": "integer", "minimum": 1, "maximum": 40, "description": "Power points (uses per battle)"},
634
+ "priority": {"type": "integer", "minimum": -5, "maximum": 5, "description": "Priority bracket"},
635
+ "flags": {"type": "array", "items": {"enum": ["contact", "explosive", "draining", "priority", "sacrifice", "reckless", "bite", "punch", "sound", "ground"]}, "description": "Move characteristics"},
636
+ "effects": {"type": "array", "items": {"$ref": "#/definitions/Effect"}, "minItems": 1, "description": "What the move does"}
637
+ },
638
+ "required": ["name", "type", "power", "accuracy", "pp", "priority", "flags", "effects"],
639
+ "additionalProperties": false
640
+ }
641
+ }
642
  }
643
  \`\`\`
644
 
645
+ **STAT GUIDELINES:**
646
+ Base the tier and stats on the object rarity:
647
+ - **common β†’ low tier**: hp/attack/defense/speed should be 10-25 (total ~40-80)
648
+ - **uncommon β†’ medium tier**: hp/attack/defense/speed should be 15-35 (total ~80-120)
649
+ - **rare β†’ high tier**: hp/attack/defense/speed should be 25-45 (total ~120-160)
650
+ - **legendary β†’ legendary tier**: hp/attack/defense/speed should be 35-50 (total ~160-200)
651
+
652
+ **MOVE DESIGN EXAMPLES:**
653
+ - **Basic Attack**: {"type": "damage", "target": "opponent", "amount": "normal"}
654
+ - **Status Move**: {"type": "applyStatus", "target": "opponent", "status": "burn", "chance": 30}
655
+ - **Self-Buff**: {"type": "modifyStats", "target": "self", "stats": {"attack": "increase"}}
656
+ - **Risk-Reward**: High power + {"type": "damage", "target": "self", "formula": "recoil", "value": 0.25}
657
 
658
  Write your response within \`\`\`json\`\`\``;
659
 
 
745
 
746
  const parsedStats = JSON.parse(cleanJson.trim());
747
 
748
+ // Validate the battle-ready monster structure
749
+ console.log('Parsed battle monster:', parsedStats);
 
 
 
 
750
 
751
+ // Ensure required fields exist
752
+ if (!parsedStats.name || !parsedStats.baseStats || !parsedStats.specialAbility || !parsedStats.movepool) {
753
+ throw new Error('Generated monster is missing required battle system fields');
 
754
  }
755
 
756
+ // Validate movepool has exactly 4 moves
757
+ if (!Array.isArray(parsedStats.movepool) || parsedStats.movepool.length !== 4) {
758
+ throw new Error('Monster movepool must contain exactly 4 moves');
 
 
 
 
 
 
 
 
 
 
 
 
759
  }
 
 
760
 
761
+ // Ensure all moves have required effect arrays
762
+ for (const move of parsedStats.movepool) {
763
+ if (!move.effects || !Array.isArray(move.effects) || move.effects.length === 0) {
764
+ throw new Error(`Move "${move.name}" is missing effects array`);
 
 
 
 
 
 
765
  }
766
  }
767
 
768
+ // Use tier from the JSON response
769
+ tier = parsedStats.tier || 'medium';
 
 
 
770
 
771
+ // Ensure the name from structured concept is used if available
772
+ if (monsterName && monsterName !== 'Unknown Monster') {
773
+ parsedStats.name = monsterName;
 
 
 
 
 
 
 
 
 
774
  }
775
+
776
+ // Ensure baseStats are numbers within reasonable ranges
777
+ if (parsedStats.baseStats) {
778
+ const statFields = ['hp', 'attack', 'defense', 'speed'];
779
+ for (const field of statFields) {
780
+ if (parsedStats.baseStats[field] !== undefined) {
781
+ parsedStats.baseStats[field] = Math.max(10, Math.min(50, parseInt(parsedStats.baseStats[field])));
782
+ }
783
+ }
784
  }
785
 
786
  const stats: PicletStats = parsedStats;
src/lib/db/piclets.ts CHANGED
@@ -1,6 +1,7 @@
1
  import { db } from './index';
2
  import type { PicletInstance, Monster, BattleMove } from './schema';
3
  import { PicletType, AttackType, getTypeFromConcept } from '../types/picletTypes';
 
4
 
5
  // Convert a generated Monster to a PicletInstance
6
  export async function monsterToPicletInstance(monster: Monster, level: number = 5): Promise<Omit<PicletInstance, 'id'>> {
@@ -8,13 +9,13 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
8
  throw new Error('Monster must have stats to create PicletInstance');
9
  }
10
 
11
- const stats = monster.stats;
12
 
13
- // Calculate base stats from the 0-100 scale
14
- const baseHp = Math.floor(stats.HP * 2 + 50);
15
- const baseAttack = Math.floor(stats.attack * 1.5 + 30);
16
- const baseDefense = Math.floor(stats.defence * 1.5 + 30);
17
- const baseSpeed = Math.floor(stats.speed * 1.5 + 30);
18
 
19
  // Field stats are variations of regular stats
20
  const baseFieldAttack = Math.floor(baseAttack * 0.8);
@@ -26,65 +27,27 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
26
 
27
  const maxHp = calculateHp(baseHp, level);
28
 
29
- // Determine primary type - use picletType from stats if available, otherwise auto-detect
30
- let primaryType: PicletType;
31
- if (monster.stats && (monster.stats as any).picletType) {
32
- // Use the type determined during stat generation, but validate it
33
- const statsType = (monster.stats as any).picletType as string;
34
- const normalizedType = statsType.toLowerCase();
35
-
36
- // Validate that the type exists in our enum
37
- const validType = Object.values(PicletType).find(type => type === normalizedType);
38
- if (validType) {
39
- primaryType = validType;
40
- } else {
41
- console.warn(`Invalid picletType "${statsType}" from stats, falling back to concept detection`);
42
- primaryType = getTypeFromConcept(monster.concept, monster.imageCaption);
43
- }
44
- } else {
45
- // Fallback to concept-based detection
46
- primaryType = getTypeFromConcept(monster.concept, monster.imageCaption);
47
  }
48
 
49
- // Create moves based on monster's abilities
50
- const moves: BattleMove[] = [
51
- {
52
- name: stats.attackActionName,
53
- type: primaryType as unknown as AttackType,
54
- power: 50,
55
- accuracy: 95,
56
- pp: 20,
57
- currentPp: 20,
58
- description: stats.attackActionDescription
59
- },
60
- {
61
- name: stats.buffActionName,
62
- type: AttackType.NORMAL,
63
- power: 0,
64
- accuracy: 100,
65
- pp: 15,
66
- currentPp: 15,
67
- description: stats.buffActionDescription
68
- },
69
- {
70
- name: stats.debuffActionName,
71
- type: AttackType.NORMAL,
72
- power: 0,
73
- accuracy: 85,
74
- pp: 15,
75
- currentPp: 15,
76
- description: stats.debuffActionDescription
77
- },
78
- {
79
- name: stats.specialActionName,
80
- type: primaryType as unknown as AttackType,
81
- power: 80,
82
- accuracy: 90,
83
- pp: 5,
84
- currentPp: 5,
85
- description: stats.specialActionDescription
86
- }
87
- ];
88
 
89
  const bst = baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed;
90
 
@@ -125,7 +88,7 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
125
  // Metadata
126
  caughtAt: new Date(),
127
  bst,
128
- tier: (stats as any).tier || 'medium', // Use tier from stats, default to medium
129
  role: 'balanced', // Could be enhanced based on stat distribution
130
  variance: 0,
131
 
 
1
  import { db } from './index';
2
  import type { PicletInstance, Monster, BattleMove } from './schema';
3
  import { PicletType, AttackType, getTypeFromConcept } from '../types/picletTypes';
4
+ import type { PicletStats } from '../types';
5
 
6
  // Convert a generated Monster to a PicletInstance
7
  export async function monsterToPicletInstance(monster: Monster, level: number = 5): Promise<Omit<PicletInstance, 'id'>> {
 
9
  throw new Error('Monster must have stats to create PicletInstance');
10
  }
11
 
12
+ const stats = monster.stats as PicletStats;
13
 
14
+ // Calculate base stats from battle-ready format
15
+ const baseHp = Math.floor(stats.baseStats.hp * 2 + 50);
16
+ const baseAttack = Math.floor(stats.baseStats.attack * 1.5 + 30);
17
+ const baseDefense = Math.floor(stats.baseStats.defense * 1.5 + 30);
18
+ const baseSpeed = Math.floor(stats.baseStats.speed * 1.5 + 30);
19
 
20
  // Field stats are variations of regular stats
21
  const baseFieldAttack = Math.floor(baseAttack * 0.8);
 
27
 
28
  const maxHp = calculateHp(baseHp, level);
29
 
30
+ // Determine primary type from battle stats
31
+ const normalizedType = stats.primaryType.toLowerCase();
32
+
33
+ // Validate that the type exists in our enum
34
+ const validType = Object.values(PicletType).find(type => type === normalizedType);
35
+ const primaryType = validType || getTypeFromConcept(monster.concept, monster.imageCaption);
36
+
37
+ if (!validType) {
38
+ console.warn(`Invalid primaryType "${stats.primaryType}" from stats, falling back to concept detection`);
 
 
 
 
 
 
 
 
 
39
  }
40
 
41
+ // Create moves from battle-ready format
42
+ const moves: BattleMove[] = stats.movepool.map(move => ({
43
+ name: move.name,
44
+ type: move.type as unknown as AttackType,
45
+ power: move.power,
46
+ accuracy: move.accuracy,
47
+ pp: move.pp,
48
+ currentPp: move.pp,
49
+ description: `${move.effects.length} effects` // Simplified description for now
50
+ }));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  const bst = baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed;
53
 
 
88
  // Metadata
89
  caughtAt: new Date(),
90
  bst,
91
+ tier: stats.tier, // Use tier from battle-ready stats
92
  role: 'balanced', // Could be enhanced based on stat distribution
93
  variance: 0,
94
 
src/lib/types/index.ts CHANGED
@@ -103,22 +103,57 @@ export interface PicletGeneratorProps {
103
  qwenClient: GradioClient | null;
104
  }
105
 
106
- // Piclet Stats Types
107
  export interface PicletStats {
108
  name: string;
109
  description: string;
110
  tier: 'low' | 'medium' | 'high' | 'legendary';
111
- HP: number; // 0-100
112
- defence: number; // 0-100
113
- attack: number; // 0-100
114
- speed: number; // 0-100
115
- specialPassiveTrait: string;
116
- attackActionName: string;
117
- attackActionDescription: string;
118
- buffActionName: string;
119
- buffActionDescription: string;
120
- debuffActionName: string;
121
- debuffActionDescription: string;
122
- specialActionName: string;
123
- specialActionDescription: string;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
 
103
  qwenClient: GradioClient | null;
104
  }
105
 
106
+ // Piclet Stats Types - now compatible with battle engine
107
  export interface PicletStats {
108
  name: string;
109
  description: string;
110
  tier: 'low' | 'medium' | 'high' | 'legendary';
111
+ primaryType: 'beast' | 'bug' | 'aquatic' | 'flora' | 'mineral' | 'space' | 'machina' | 'structure' | 'culture' | 'cuisine';
112
+ secondaryType?: 'beast' | 'bug' | 'aquatic' | 'flora' | 'mineral' | 'space' | 'machina' | 'structure' | 'culture' | 'cuisine' | null;
113
+ baseStats: {
114
+ hp: number;
115
+ attack: number;
116
+ defense: number;
117
+ speed: number;
118
+ };
119
+ nature: string;
120
+ specialAbility: {
121
+ name: string;
122
+ description: string;
123
+ effects?: BattleEffect[];
124
+ triggers?: AbilityTrigger[];
125
+ };
126
+ movepool: BattleMove[];
127
+ }
128
+
129
+ // Battle system effect types for PicletStats
130
+ export interface BattleEffect {
131
+ type: 'damage' | 'modifyStats' | 'applyStatus' | 'heal' | 'manipulatePP' | 'fieldEffect' | 'counter' | 'priority' | 'removeStatus' | 'mechanicOverride';
132
+ target: 'self' | 'opponent' | 'allies' | 'all' | 'attacker' | 'field' | 'playerSide' | 'opponentSide';
133
+ condition?: string;
134
+ // Additional properties based on effect type
135
+ amount?: string;
136
+ formula?: string;
137
+ value?: number;
138
+ stats?: { [key: string]: string };
139
+ status?: string;
140
+ chance?: number;
141
+ [key: string]: any; // Allow additional properties for different effect types
142
+ }
143
+
144
+ export interface AbilityTrigger {
145
+ event: string;
146
+ condition?: string;
147
+ effects: BattleEffect[];
148
+ }
149
+
150
+ export interface BattleMove {
151
+ name: string;
152
+ type: 'beast' | 'bug' | 'aquatic' | 'flora' | 'mineral' | 'space' | 'machina' | 'structure' | 'culture' | 'cuisine' | 'normal';
153
+ power: number;
154
+ accuracy: number;
155
+ pp: number;
156
+ priority: number;
157
+ flags: string[];
158
+ effects: BattleEffect[];
159
  }