File size: 4,652 Bytes
d05c291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// Pawfoil core: turns a pet's name + kind into a collectible card's stats, moves and rarity (no DOM).
// Deterministic for a given name, kind and roll number, so a re-roll is reproducible and testable.
// (c) 2026 CyberMax. All rights reserved.
import { rng } from './cmx.js';

export const KINDS = {
  dog: { label: 'Dog', emoji: '🐶', types: ['Zoomies', 'Loyal', 'Snack', 'Chaos'] },
  cat: { label: 'Cat', emoji: '🐱', types: ['Shadow', 'Royal', 'Chaos', 'Nap'] },
  bunny: { label: 'Bunny', emoji: '🐰', types: ['Hop', 'Fluff', 'Snack', 'Shadow'] },
  bird: { label: 'Bird', emoji: '🐦', types: ['Sky', 'Song', 'Chaos', 'Royal'] },
  other: { label: 'Other', emoji: '🐾', types: ['Mystery', 'Fluff', 'Chaos', 'Nap'] },
};
export const STATS = ['Cuteness', 'Chaos', 'Snack drive', 'Nap power', 'Zoomies', 'Loyalty'];
export const RARITY = [
  { id: 'common', label: 'Common', p: 0.5, stars: 1 },
  { id: 'rare', label: 'Rare', p: 0.3, stars: 2 },
  { id: 'epic', label: 'Epic', p: 0.15, stars: 3 },
  { id: 'legendary', label: 'Legendary', p: 0.05, stars: 4 },
];
const MOVES = {
  any: [['Puppy Eyes', 'Opponent forgets why they were mad.'], ['Loaf Mode', 'Becomes bread. Immune to chores.'], ['3 AM Zoomies', 'Runs laps. Wakes the house.'],
    ['Sock Heist', 'Steals one sock. Never returns it.'], ['Treat Radar', 'Detects a wrapper from 3 rooms away.'], ['Lap Claim', 'Sits on you. You can never leave.'],
    ['Dramatic Sigh', 'Guilt-trips everyone in range.'], ['Zoom Call Cameo', 'Appears on camera at the worst time.'], ['Vet Vanish', 'Disappears when the carrier comes out.'],
    ['Sunbeam Charge', 'Recharges fully in any sunny spot.'], ['Head Tilt', 'Confuses the enemy with pure cuteness.'], ['Couch Fortress', 'Holds the best seat all day.']],
  dog: [['Tail Helicopter', 'Knocks everything off the coffee table.'], ['Doorbell Alarm', 'Barks at every delivery, forever.'], ['Fetch Loop', 'Infinite fetch. You tire first.']],
  cat: [['Desk Sweep', 'Pushes one item off the table, slowly.'], ['Keyboard Nap', 'Types "jjjjjjjjj" into your email.'], ['Box Fit', 'If it fits, it sits.']],
  bunny: [['Binky Burst', 'A joyful jump that dodges all attacks.'], ['Cable Snip', 'Chews one charger. Critical hit.'], ['Hay Storm', 'Covers the floor in hay.']],
  bird: [['Morning Concert', 'Sings at sunrise. No snooze.'], ['Shoulder Ride', 'Takes the high ground.'], ['Mimic', 'Copies the microwave beep perfectly.']],
  other: [['Mystery Stare', 'Nobody knows what it is thinking.'], ['Tiny Speedrun', 'Escapes the enclosure in record time.'], ['Night Shift', 'Most active when you sleep.']],
};

export function cleanPetName(s) {
  return String(s || '').normalize('NFKC').replace(/[^\p{L}\p{N} '.-]/gu, '').replace(/\s+/g, ' ').trim().slice(0, 18);
}

/** Card data for a pet. roll = 0 for the first card, 1, 2, … for re-rolls. pick() chooses rarity unless forced (Fun Pass). */
export function makeCard(name, kind = 'dog', roll = 0, forceRarity = null) {
  const k = KINDS[kind] ? kind : 'other';
  const r = rng(`pawfoil:${cleanPetName(name).toLowerCase()}:${k}:${roll}`);
  let x = r(), rarity = RARITY[0];
  for (const t of RARITY) { if (x < t.p) { rarity = t; break; } x -= t.p; }
  if (forceRarity) rarity = RARITY.find((t) => t.id === forceRarity) || rarity;
  const boost = { common: 0, rare: 6, epic: 12, legendary: 20 }[rarity.id];
  const stats = STATS.map((s) => ({ s, v: Math.min(99, 35 + Math.floor(r() * 58) + boost) }));
  stats[0].v = Math.max(stats[0].v, 88); // every pet is very cute; that is the one fixed fact
  const pool = [...MOVES[k], ...MOVES.any];
  const m1 = pool.splice(Math.floor(r() * MOVES[k].length), 1)[0];
  const m2 = pool.splice(Math.floor(r() * pool.length), 1)[0];
  const type = KINDS[k].types[Math.floor(r() * KINDS[k].types.length)];
  const hp = 60 + Math.floor(r() * 9) * 10 + boost * 2;
  const power = Math.round(stats.reduce((a, b) => a + b.v, 0) / stats.length);
  const number = 1 + Math.floor(r() * 999);
  return { name: cleanPetName(name) || 'Mystery Pet', kind: k, kindLabel: KINDS[k].label, emoji: KINDS[k].emoji, type, rarity: rarity.id, rarityLabel: rarity.label, stars: rarity.stars,
    hp, power, stats, moves: [{ name: m1[0], text: m1[1], dmg: 10 * (2 + Math.floor(r() * 7)) }, { name: m2[0], text: m2[1], dmg: 10 * (3 + Math.floor(r() * 8)) }], number };
}

export const FRAMES = {
  common: { a: '#E9E4D8', b: '#C9BFA8', ink: '#11141B', foil: false },
  rare: { a: '#BFE3FF', b: '#5BA8E8', ink: '#0B2238', foil: false },
  epic: { a: '#E5C8FF', b: '#9B5BE8', ink: '#1E0B38', foil: true },
  legendary: { a: '#FFE9A8', b: '#E8A93A', ink: '#2B1A00', foil: true },
};