codekeep 0.5.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/index.js +6 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{Command as ur}from"commander";import{render as pr}from"ink";import{useState as B,useCallback as J,useEffect as sr}from"react";import{Box as ce,Text as w,useApp as ir,useInput as cr,useStdout as lr}from"ink";var ke=5,ee=4,Te=3,He=5,te=70,de=[{id:"strike",name:"Strike",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 6 damage to an enemy.",effects:[{type:"damage",value:6,target:"single"}]},{id:"guard",name:"Guard",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 5 Block.",effects:[{type:"block",value:5,target:"self"}]},{id:"bolster",name:"Bolster",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 8 Block.",effects:[{type:"block",value:8,target:"self"}]},{id:"spark",name:"Spark",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 4 damage to all enemies in a column.",effects:[{type:"damage",value:4,target:"column"}]},{id:"reinforce",name:"Reinforce",cost:2,type:"cast",category:"fortification",rarity:"common",description:"Gain 12 Block.",effects:[{type:"block",value:12,target:"self"}]},{id:"barrage",name:"Barrage",cost:2,type:"cast",category:"armament",rarity:"common",description:"Deal 4 damage to all enemies.",effects:[{type:"damage",value:4,target:"all"}]},{id:"mend",name:"Mend",cost:1,type:"cast",category:"edict",rarity:"common",description:"Heal 4 Gate HP.",effects:[{type:"heal",value:4,target:"self"}]},{id:"lookout",name:"Lookout",cost:0,type:"cast",category:"edict",rarity:"common",description:"Draw 1 card.",effects:[{type:"draw",value:1}]},{id:"brace",name:"Brace",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 4 Block. Draw 1 card.",effects:[{type:"block",value:4,target:"self"},{type:"draw",value:1}]},{id:"ember",name:"Ember",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 8 damage to an enemy.",effects:[{type:"damage",value:8,target:"single"}]},{id:"slash",name:"Slash",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 5 damage to an enemy. Draw 1 card.",effects:[{type:"damage",value:5,target:"single"},{type:"draw",value:1}]},{id:"rally",name:"Rally",cost:1,type:"cast",category:"edict",rarity:"common",description:"Gain 3 Block. Draw 2 cards.",effects:[{type:"block",value:3,target:"self"},{type:"draw",value:2}]},{id:"salvo",name:"Salvo",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 3 damage to all enemies.",effects:[{type:"damage",value:3,target:"all"}]},{id:"patch",name:"Patch",cost:0,type:"cast",category:"fortification",rarity:"common",description:"Gain 3 Block.",effects:[{type:"block",value:3,target:"self"}]},{id:"flare",name:"Flare",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 6 damage to all enemies in a column.",effects:[{type:"damage",value:6,target:"column"}]},{id:"cleave",name:"Cleave",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 10 damage to an enemy.",effects:[{type:"damage",value:10,target:"single"}]},{id:"iron_wall",name:"Iron Wall",cost:2,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 16 Block.",effects:[{type:"block",value:16,target:"self"}]},{id:"blitz",name:"Blitz",cost:1,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 4 damage to an enemy twice.",effects:[{type:"damage",value:4,target:"single"},{type:"damage",value:4,target:"single"}]},{id:"volley",name:"Volley",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 6 damage to all enemies.",effects:[{type:"damage",value:6,target:"all"}]},{id:"resurgence",name:"Resurgence",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Heal 8 Gate HP.",effects:[{type:"heal",value:8,target:"self"}]},{id:"keen_eye",name:"Keen Eye",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Draw 3 cards.",effects:[{type:"draw",value:3}]},{id:"expose",name:"Expose",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Vulnerable to an enemy.",effects:[{type:"vulnerable",value:2,target:"single"}]},{id:"weaken",name:"Weaken",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Weak to an enemy.",effects:[{type:"weak",value:2,target:"single"}]},{id:"bash",name:"Bash",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 7 damage. Apply 1 Vulnerable.",effects:[{type:"damage",value:7,target:"single"},{type:"vulnerable",value:1,target:"single"}]},{id:"shield_bash",name:"Shield Bash",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 6 Block. Deal 3 damage.",effects:[{type:"block",value:6,target:"self"},{type:"damage",value:3,target:"single"}]},{id:"ignite",name:"Ignite",cost:1,type:"cast",category:"armament",rarity:"common",description:"Apply 3 Burn to a column.",effects:[{type:"burn",value:3,target:"column"}]},{id:"scout",name:"Scout",cost:0,type:"cast",category:"edict",rarity:"common",description:"Draw 2 cards.",effects:[{type:"draw",value:2}]},{id:"fortify",name:"Fortify",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 10 Block.",effects:[{type:"block",value:10,target:"self"}]},{id:"sundering_strike",name:"Sundering Strike",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 12 damage. Apply 2 Vulnerable.",effects:[{type:"damage",value:12,target:"single"},{type:"vulnerable",value:2,target:"single"}]},{id:"pale_fire",name:"Pale Fire",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 5 damage to all. Apply 2 Burn.",effects:[{type:"damage",value:5,target:"all"},{type:"burn",value:2,target:"all"}]},{id:"shield_wall",name:"Shield Wall",cost:2,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 20 Block.",effects:[{type:"block",value:20,target:"self"}]},{id:"battle_cry",name:"Battle Cry",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Gain 1 Resolve. Draw 1 card.",effects:[{type:"resolve",value:1},{type:"draw",value:1}]},{id:"intimidate",name:"Intimidate",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Weak to all enemies.",effects:[{type:"weak",value:2,target:"all"}]},{id:"hemorrhage",name:"Hemorrhage",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 8 damage. Apply 4 Burn.",effects:[{type:"damage",value:8,target:"single"},{type:"burn",value:4,target:"single"}]},{id:"restoration",name:"Restoration",cost:2,type:"cast",category:"edict",rarity:"uncommon",description:"Heal 12 Gate HP. Gain 4 Block.",effects:[{type:"heal",value:12,target:"self"},{type:"block",value:4,target:"self"}]},{id:"precision",name:"Precision",cost:1,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 15 damage to an enemy.",effects:[{type:"damage",value:15,target:"single"}]},{id:"firestorm",name:"Firestorm",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 8 damage to a column. Apply 3 Burn.",effects:[{type:"damage",value:8,target:"column"},{type:"burn",value:3,target:"column"}]},{id:"deep_guard",name:"Deep Guard",cost:1,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 7 Block. Draw 1 card.",effects:[{type:"block",value:7,target:"self"},{type:"draw",value:1}]},{id:"inferno",name:"Inferno",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 12 damage to all enemies.",effects:[{type:"damage",value:12,target:"all"}]},{id:"fortress",name:"Fortress",cost:3,type:"cast",category:"fortification",rarity:"rare",description:"Gain 25 Block. Gain 1 Resolve.",effects:[{type:"block",value:25,target:"self"},{type:"resolve",value:1}]},{id:"annihilate",name:"Annihilate",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 20 damage to an enemy.",effects:[{type:"damage",value:20,target:"single"}]},{id:"wardens_wrath",name:"Warden's Wrath",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 8 damage to all. Apply 2 Vulnerable to all.",effects:[{type:"damage",value:8,target:"all"},{type:"vulnerable",value:2,target:"all"}]},{id:"iron_bastion",name:"Iron Bastion",cost:3,type:"cast",category:"fortification",rarity:"rare",description:"Gain 30 Block. Heal 5.",effects:[{type:"block",value:30,target:"self"},{type:"heal",value:5,target:"self"}]},{id:"rally_the_keep",name:"Rally the Keep",cost:2,type:"cast",category:"edict",rarity:"rare",description:"Draw 4 cards. Gain 1 Resolve.",effects:[{type:"draw",value:4},{type:"resolve",value:1}]},{id:"conflagration",name:"Conflagration",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Apply 6 Burn to all enemies. Deal 5 damage to all.",effects:[{type:"burn",value:6,target:"all"},{type:"damage",value:5,target:"all"}]},{id:"pale_ward",name:"Pale Ward",cost:2,type:"cast",category:"fortification",rarity:"rare",description:"Gain 15 Block. Apply 2 Weak to all enemies.",effects:[{type:"block",value:15,target:"self"},{type:"weak",value:2,target:"all"}]},{id:"cataclysm",name:"Cataclysm",cost:4,type:"cast",category:"armament",rarity:"legendary",description:"Deal 30 damage to all enemies. Take 10 Gate damage.",effects:[{type:"damage",value:30,target:"all"}]},{id:"keeps_resolve",name:"Keep's Resolve",cost:0,type:"cast",category:"edict",rarity:"legendary",description:"Gain 3 Resolve. Draw 3 cards.",effects:[{type:"resolve",value:3},{type:"draw",value:3}]},{id:"eternal_wall",name:"Eternal Wall",cost:4,type:"cast",category:"fortification",rarity:"legendary",description:"Gain 50 Block. Heal 10 Gate HP.",effects:[{type:"block",value:50,target:"self"},{type:"heal",value:10,target:"self"}]},{id:"barricade",name:"Barricade",cost:1,type:"emplace",category:"fortification",rarity:"common",description:"Cast: Gain 4 Block. Emplace: 8 HP structure, +2 Block/turn.",effects:[{type:"block",value:4,target:"self"}],emplaceCost:1,emplaceHp:8,emplaceEffects:[{type:"block",value:2,target:"self"}]},{id:"turret",name:"Turret",cost:2,type:"emplace",category:"armament",rarity:"uncommon",description:"Cast: Deal 5 damage. Emplace: 6 HP, deals 3 damage to column/turn.",effects:[{type:"damage",value:5,target:"single"}],emplaceCost:2,emplaceHp:6,emplaceEffects:[{type:"damage",value:3,target:"column"}]},{id:"beacon",name:"Beacon",cost:1,type:"emplace",category:"edict",rarity:"uncommon",description:"Cast: Draw 1 card. Emplace: 5 HP, heals 2 Gate HP/turn.",effects:[{type:"draw",value:1}],emplaceCost:1,emplaceHp:5,emplaceEffects:[{type:"heal",value:2,target:"self"}]},{id:"ward_stone",name:"Ward Stone",cost:1,type:"emplace",category:"fortification",rarity:"common",description:"Cast: Gain 3 Block. Emplace: 10 HP, applies 1 Weak to enemies in column.",effects:[{type:"block",value:3,target:"self"}],emplaceCost:1,emplaceHp:10,emplaceEffects:[{type:"weak",value:1,target:"column"}]},{id:"siphon",name:"Siphon",cost:2,type:"emplace",category:"wild",rarity:"rare",description:"Cast: Deal 4 damage to all. Emplace: 4 HP, deals 2 damage to adjacent columns.",effects:[{type:"damage",value:4,target:"all"}],emplaceCost:2,emplaceHp:4,emplaceEffects:[{type:"damage",value:2,target:"adjacent"}]},{id:"watchtower",name:"Watchtower",cost:1,type:"emplace",category:"edict",rarity:"common",description:"Cast: Draw 2 cards. Emplace: 4 HP, applies 1 Vulnerable to enemies in column.",effects:[{type:"draw",value:2}],emplaceCost:1,emplaceHp:4,emplaceEffects:[{type:"vulnerable",value:1,target:"column"}]},{id:"flame_pit",name:"Flame Pit",cost:2,type:"emplace",category:"armament",rarity:"uncommon",description:"Cast: Deal 6 damage to column. Emplace: 5 HP, deals 4 damage to column/turn.",effects:[{type:"damage",value:6,target:"column"}],emplaceCost:2,emplaceHp:5,emplaceEffects:[{type:"damage",value:4,target:"column"}]},{id:"bulwark",name:"Bulwark",cost:2,type:"emplace",category:"fortification",rarity:"rare",description:"Cast: Gain 10 Block. Emplace: 15 HP, +3 Block/turn.",effects:[{type:"block",value:10,target:"self"}],emplaceCost:2,emplaceHp:15,emplaceEffects:[{type:"block",value:3,target:"self"}]},{id:"spike_trap",name:"Spike Trap",cost:1,type:"emplace",category:"armament",rarity:"common",description:"Cast: Deal 3 damage. Emplace: 3 HP, deals 2 damage to column/turn.",effects:[{type:"damage",value:3,target:"single"}],emplaceCost:1,emplaceHp:3,emplaceEffects:[{type:"damage",value:2,target:"column"}]},{id:"sanctum",name:"Sanctum",cost:3,type:"emplace",category:"edict",rarity:"rare",description:"Cast: Heal 6, draw 1. Emplace: 8 HP, heals 3 Gate HP/turn, +1 Block/turn.",effects:[{type:"heal",value:6,target:"self"},{type:"draw",value:1}],emplaceCost:3,emplaceHp:8,emplaceEffects:[{type:"heal",value:3,target:"self"},{type:"block",value:1,target:"self"}]}],it=["strike","strike","strike","guard","guard","spark","ember","brace","bolster","mend"];function G(e){return de.find(t=>t.id===e)}var ma=[{id:"hollow",name:"Hollow",symbol:"\u2620",hp:18,damage:6,speed:1,act:1,description:"An empty shape from the Pale. Advances steadily."},{id:"needle",name:"Needle",symbol:"\u2191",hp:10,damage:4,speed:2,act:1,description:"Fast and fragile. Advances two rows per turn."},{id:"shade",name:"Shade",symbol:"\u25C8",hp:22,damage:8,speed:1,act:1,description:"A dense fragment of the Pale. Hits hard."},{id:"wisp",name:"Wisp",symbol:"\u25CB",hp:8,damage:3,speed:1,act:1,description:"Fragile but numerous. Appears in swarms."},{id:"husk",name:"Husk",symbol:"\u2593",hp:30,damage:5,speed:1,act:1,description:"Armored shell. Slow but resilient."},{id:"wraith",name:"Wraith",symbol:"\u2248",hp:15,damage:7,speed:2,act:2,description:"Drifts through emplacements. Ignores structures."},{id:"breaker",name:"Breaker",symbol:"\u2692",hp:25,damage:10,speed:1,act:2,description:"Targets emplacements first. Destroys structures."},{id:"flanker",name:"Flanker",symbol:"\u2194",hp:14,damage:6,speed:1,act:2,description:"Shifts columns before attacking."},{id:"shielder",name:"Shielder",symbol:"\u25C7",hp:20,damage:4,speed:1,act:2,description:"Grants shield to adjacent enemies."},{id:"echo",name:"Echo",symbol:"\u221E",hp:35,damage:12,speed:1,act:3,description:"A memory of something that should not exist."},{id:"boss_suture",name:"The Suture",symbol:"\u25C8",hp:60,damage:8,speed:1,act:1,description:"Stitched from fragments of the Pale. The first true threat."},{id:"boss_archivist",name:"The Archivist",symbol:"\u25A3",hp:90,damage:10,speed:1,act:2,description:"Keeper of forgotten records. Debuffs and shields methodically."},{id:"boss_pale",name:"The Pale Itself",symbol:"\u25C9",hp:130,damage:12,speed:1,act:3,description:"The void given form. Three phases of escalating horror."}];function j(e){return ma.find(t=>t.id===e)}var Be=[{id:"heal_potion",name:"Mending Draught",description:"Heal 15 Gate HP.",effects:[{type:"heal",value:15,target:"self"}]},{id:"damage_potion",name:"Fire Flask",description:"Deal 10 damage to all enemies in a column.",effects:[{type:"damage",value:10,target:"column"}]},{id:"block_potion",name:"Iron Tonic",description:"Gain 15 Block.",effects:[{type:"block",value:15,target:"self"}]},{id:"draw_potion",name:"Seer's Ink",description:"Draw 3 cards.",effects:[{type:"draw",value:3}]},{id:"resolve_potion",name:"Warden's Flame",description:"Gain 2 Resolve this turn.",effects:[{type:"resolve",value:2}]}],xe={combat:15,elite:30,boss:50};import{writeFileSync as Fe,readFileSync as Da,mkdirSync as Aa,existsSync as Ct,renameSync as Ma,unlinkSync as Er}from"fs";import{join as St,dirname as Ha}from"path";import{homedir as Ba}from"os";function N(e){let t=e|0;return()=>{t=t+1831565813|0;let a=Math.imul(t^t>>>15,1|t);return a=a+Math.imul(a^a>>>7,61|a)^a,((a^a>>>14)>>>0)/4294967296}}function V(e){let t=0;for(let a=0;a<e.length;a++){let r=e.charCodeAt(a);t=(t<<5)-t+r|0}return t}function lt(e,t){let a=[...e];for(let r=a.length-1;r>0;r--){let o=Math.floor(t()*(r+1));[a[r],a[o]]=[a[o],a[r]]}return a}var ua=1;function Ie(e){return{instanceId:`card-${ua++}`,defId:e,upgraded:!1}}function Ve(){return it.map(Ie)}function pa(e,t){return lt(e,t)}function Oe(e,t,a,r){let o=[],n=[...e],i=[...t];for(let l=0;l<a;l++){if(n.length===0){if(i.length===0)break;n=lt(i,r),i=[]}o.push(n.shift())}return{drawn:o,newDrawPile:n,newDiscardPile:i}}var fa=1;function dt(e,t){let a=j(e);if(!a)throw new Error(`Unknown enemy template: ${e}`);return{instanceId:`enemy-${fa++}`,templateId:e,hp:a.hp,maxHp:a.hp,column:Math.max(0,Math.min(t,ke-1)),row:0,intent:null,statusEffects:[]}}function Ue(e,t){let a=j(e.templateId);return a?t()<.5?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column}:{type:"advance",value:1}}function ha(e,t,a){return a<0||a>=e.columns.length||e.columns[a].emplacement||!t.emplaceHp||!t.emplaceEffects?!1:(e.columns[a].emplacement={cardDefId:t.id,hp:t.emplaceHp,maxHp:t.emplaceHp,effects:t.emplaceEffects},e.events.push({type:"emplacement_placed",turn:e.turn,data:{cardId:t.id,column:a}}),!0)}function ga(e){for(let t of e.columns)if(t.emplacement){for(let a of t.emplacement.effects)ya(e,t.index,a);e.events.push({type:"emplacement_triggered",turn:e.turn,data:{column:t.index,cardId:t.emplacement.cardDefId}})}}function ya(e,t,a){let r=e.columns[t];switch(a.type){case"damage":{if(a.target==="column")for(let o of r.enemies)o.hp-=a.value;else if(a.target==="adjacent")for(let o=Math.max(0,t-1);o<=Math.min(e.columns.length-1,t+1);o++)for(let n of e.columns[o].enemies)n.hp-=a.value;else for(let o of r.enemies)o.hp-=a.value;break}case"block":e.gateBlock+=a.value;break;case"heal":e.gateHp=Math.min(e.gateMaxHp,e.gateHp+a.value);break;case"weak":for(let o of r.enemies){let n=o.statusEffects.find(i=>i.type==="weak");n?n.stacks+=a.value:o.statusEffects.push({type:"weak",stacks:a.value,duration:2})}break;case"vulnerable":for(let o of r.enemies){let n=o.statusEffects.find(i=>i.type==="vulnerable");n?n.stacks+=a.value:o.statusEffects.push({type:"vulnerable",stacks:a.value,duration:2})}break}}function va(e){e.statusEffects=e.statusEffects.map(t=>({...t,duration:t.duration-1})).filter(t=>t.duration>0&&t.stacks>0)}function ae(e,t,a,r=2){let o=e.statusEffects.find(n=>n.type===t);o?(o.stacks+=a,o.duration=Math.max(o.duration,r)):e.statusEffects.push({type:t,stacks:a,duration:r})}function me(e,t){return e.statusEffects.find(r=>r.type===t)?.stacks??0}function wa(e){let t=1;return me(e,"vulnerable")>0&&(t*=1.5),t}function ba(e){let t=1;return me(e,"weak")>0&&(t*=.75),me(e,"empowered")>0&&(t*=1+me(e,"empowered")*.25),t}function ka(e){let t=me(e,"burn");if(t>0){e.hp-=t;let a=e.statusEffects.find(r=>r.type==="burn");return a&&(a.stacks=Math.max(0,a.stacks-1)),t}return 0}function mt(e,t,a=te,r=te,o=[{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"needle",column:2}]){let n=N(t),i=pa(e,n),l=Array.from({length:ke},(y,k)=>({index:k,enemies:[],emplacement:null}));for(let y of o){let k=dt(y.templateId,y.column);l[k.column].enemies.push(k)}let{drawn:s,newDrawPile:u,newDiscardPile:f}=Oe(i,[],He,n),p={columns:l,hand:s,drawPile:u,discardPile:f,exhaustPile:[],gateHp:a,gateMaxHp:r,gateBlock:0,resolve:Te,maxResolve:Te,turn:1,phase:"player",outcome:"undecided",events:[],seed:t};for(let y of l)for(let k of y.enemies)k.intent=Ue(k,n);return E(p,"turn_start",{turn:1}),p}function E(e,t,a){e.events.push({type:t,turn:e.turn,data:a})}function ut(e,t,a,r=!1){if(e.phase!=="player")return e;let o=e.hand[t];if(!o)return e;let n=G(o.defId);if(!n)return e;if(r&&n.type==="emplace"&&n.emplaceCost!==void 0){if(n.emplaceCost>e.resolve)return e;e.resolve-=n.emplaceCost,e.hand.splice(t,1),ha(e,n,a??0),e.exhaustPile.push(o)}else{if(n.cost>e.resolve)return e;e.resolve-=n.cost,e.hand.splice(t,1);for(let i of n.effects)Ta(e,i,a??0);e.discardPile.push(o)}return E(e,"card_played",{cardId:o.defId,targetColumn:a,asEmplace:r}),We(e),e}function Ta(e,t,a){switch(t.type){case"damage":{if(t.target==="all")for(let r of e.columns)for(let o of r.enemies)Ge(e,o,t.value);else if(t.target==="column"){let r=e.columns[a];if(r)for(let o of r.enemies)Ge(e,o,t.value)}else{let r=e.columns[a];if(r&&r.enemies.length>0){let o=r.enemies.reduce((n,i)=>n.row>=i.row?n:i);Ge(e,o,t.value)}}Ne(e);break}case"block":e.gateBlock+=t.value,E(e,"block_gained",{value:t.value});break;case"heal":e.gateHp=Math.min(e.gateMaxHp,e.gateHp+t.value);break;case"draw":{let{drawn:r,newDrawPile:o,newDiscardPile:n}=Oe(e.drawPile,e.discardPile,t.value,N(e.seed+e.turn));e.hand.push(...r),e.drawPile=o,e.discardPile=n;break}case"resolve":e.resolve=Math.min(e.maxResolve+5,e.resolve+t.value);break;case"vulnerable":{let r=e.columns[a];if(r){for(let o of r.enemies)ae(o,"vulnerable",t.value,3);E(e,"status_applied",{type:"vulnerable",value:t.value,column:a})}break}case"weak":{let r=e.columns[a];if(r){for(let o of r.enemies)ae(o,"weak",t.value,3);E(e,"status_applied",{type:"weak",value:t.value,column:a})}break}case"burn":{if(t.target==="all")for(let r of e.columns)for(let o of r.enemies)ae(o,"burn",t.value,99);else{let r=e.columns[a];if(r)for(let o of r.enemies)ae(o,"burn",t.value,99)}break}}}function Ge(e,t,a){let r=wa(t),o=Math.floor(a*r);t.hp-=o,E(e,"damage_dealt",{targetId:t.instanceId,damage:o})}function Ne(e){for(let t of e.columns)t.enemies=t.enemies.filter(a=>a.hp<=0?(E(e,"enemy_killed",{enemyId:a.instanceId,templateId:a.templateId}),!1):!0)}function pt(e){return e.phase!=="player"||(E(e,"turn_end",{turn:e.turn}),e.phase="enemy",xa(e)),e}function xa(e){let t=N(e.seed+e.turn*31);for(let n of e.columns)for(let i of n.enemies){if(i.hp<=0)continue;let l=i.intent??{type:"advance",value:1};_a(e,i,l)}if(Ne(e),We(e),e.outcome!=="undecided"||(e.turn++,e.phase="player",e.resolve=Te,e.gateBlock=0,ga(e),Ne(e),We(e),e.outcome!=="undecided"))return;e.discardPile.push(...e.hand),e.hand=[];let{drawn:a,newDrawPile:r,newDiscardPile:o}=Oe(e.drawPile,e.discardPile,He,t);e.hand=a,e.drawPile=r,e.discardPile=o;for(let n of e.columns)for(let i of n.enemies)i.intent=Ue(i,t);E(e,"turn_start",{turn:e.turn})}function _a(e,t,a){let r=j(t.templateId),o=ba(t);switch(ka(t),va(t),a.type){case"advance":if(t.row=Math.min(ee-1,t.row+(r?.speed??1)),E(e,"enemy_advance",{enemyId:t.instanceId,row:t.row}),t.row>=ee-1){let n=e.columns[t.column];if(n.emplacement)n.emplacement.hp-=r?.damage??4,n.emplacement.hp<=0&&(E(e,"emplacement_destroyed",{column:t.column}),n.emplacement=null);else{let i=Math.floor((r?.damage??4)*o),l=Math.min(e.gateBlock,i);e.gateBlock-=l,e.gateHp-=i-l,E(e,"gate_hit",{enemyId:t.instanceId,damage:i-l,blocked:l})}}break;case"attack":{let n=Math.floor((r?.damage??4)*o),i=Math.min(e.gateBlock,n);e.gateBlock-=i,e.gateHp-=n-i,E(e,"gate_hit",{enemyId:t.instanceId,damage:n-i,blocked:i});break}case"buff":ae(t,"empowered",a.value,3);break;case"debuff":{e.gateBlock=Math.max(0,e.gateBlock-a.value*2);break}case"shield":{let n=e.columns[t.column];for(let i of n.enemies)ae(i,"fortified",a.value,2);break}case"summon":{let n=e.columns.findIndex(i=>i.enemies.length===0);if(n>=0){let i=dt("wisp",n);e.columns[n].enemies.push(i),i.intent=Ue(i,N(e.seed+e.turn*97))}break}}}function We(e){if(e.gateHp<=0){e.gateHp=0,e.outcome="lose",e.phase="ended",E(e,"combat_end",{outcome:"lose"});return}e.columns.reduce((a,r)=>a+r.enemies.length,0)===0&&(e.outcome="win",e.phase="ended",E(e,"combat_end",{outcome:"win"}))}function Ke(e,t=3,a=[]){let r=new Set(["strike","guard","bolster","brace","mend",...a]),n=de.filter(u=>!r.has(u.id)).map(u=>({card:u,weight:u.rarity==="common"?6:u.rarity==="uncommon"?3:1})),i=n.reduce((u,f)=>u+f.weight,0),l=[],s=new Set;for(let u=0;u<t&&n.length>0;u++){let f=e()*i;for(let p of n)if(!s.has(p.card.id)&&(f-=p.weight,f<=0)){l.push(p.card),s.add(p.card.id);break}if(l.length<=u){let p=n.filter(y=>!s.has(y.card.id));if(p.length>0){let y=p[Math.floor(e()*p.length)];l.push(y.card),s.add(y.card.id)}}}return l}var Ia=[{name:"Scout Party",enemies:[{templateId:"hollow",column:1},{templateId:"hollow",column:3}]},{name:"Swift Assault",enemies:[{templateId:"needle",column:0},{templateId:"needle",column:2},{templateId:"needle",column:4}]},{name:"Pale Vanguard",enemies:[{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"needle",column:2}]},{name:"Wisp Swarm",enemies:[{templateId:"wisp",column:0},{templateId:"wisp",column:1},{templateId:"wisp",column:2},{templateId:"wisp",column:3},{templateId:"wisp",column:4}]},{name:"Heavy Patrol",enemies:[{templateId:"shade",column:1},{templateId:"hollow",column:3}]},{name:"Armored Column",enemies:[{templateId:"husk",column:2},{templateId:"wisp",column:0},{templateId:"wisp",column:4}]}],Ca=[{name:"The Dark Tide",enemies:[{templateId:"shade",column:1},{templateId:"shade",column:3},{templateId:"needle",column:2}]},{name:"Hollow Legion",enemies:[{templateId:"hollow",column:0},{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"hollow",column:4}]}],Sa=[{name:"Wraith Drift",enemies:[{templateId:"wraith",column:1},{templateId:"wraith",column:3}]},{name:"Breach Team",enemies:[{templateId:"breaker",column:2},{templateId:"flanker",column:0},{templateId:"flanker",column:4}]},{name:"Shield Wall",enemies:[{templateId:"shielder",column:2},{templateId:"hollow",column:1},{templateId:"hollow",column:3}]}],Ea=[{name:"Echoes of the End",enemies:[{templateId:"echo",column:1},{templateId:"echo",column:3}]},{name:"Final Surge",enemies:[{templateId:"echo",column:2},{templateId:"wraith",column:0},{templateId:"wraith",column:4},{templateId:"breaker",column:1}]}];function ft(e,t,a=!1){let r;if(a)r=Ca;else switch(e){case 2:r=Sa;break;case 3:r=Ea;break;default:r=Ia;break}let o=Math.floor(t()*r.length);return{...r[o],isElite:a}}var _e=12,ct=4;function Pa(e,t,a){if(e===0)return"combat";if(e===t-1)return"boss";let r=a();return e===Math.floor(t/2)?r<.5?"rest":"shop":r<.5?"combat":r<.65?"elite":r<.78?"event":r<.88?"rest":"shop"}function ht(e,t){let a=N(t),r=[],o=[];for(let n=0;n<_e;n++){let i=[],l=n===0||n===_e-1?1:Math.floor(a()*2)+2,s=new Set;for(;s.size<l;)s.add(Math.floor(a()*ct));for(let u=0;u<ct;u++)if(s.has(u)){let f=Pa(n,_e,a),p={id:`node-${e}-${n}-${u}`,type:f,row:n,column:u,connections:[],visited:!1};i.push(p),r.push(p)}else i.push(null);o.push(i)}for(let n=0;n<_e-1;n++){let i=o[n].filter(s=>s!==null),l=o[n+1].filter(s=>s!==null);if(l.length!==0){for(let s of i){let u=l.map(f=>({node:f,dist:Math.abs(f.column-s.column)})).sort((f,p)=>f.dist-p.dist);s.connections.push(u[0].node.id),u.length>1&&a()<.4&&s.connections.push(u[1].node.id)}for(let s of l)!i.some(f=>f.connections.includes(s.id))&&i.length>0&&i.map(p=>({node:p,dist:Math.abs(p.column-s.column)})).sort((p,y)=>p.dist-y.dist)[0].node.connections.push(s.id)}}return{act:e,nodes:r}}function ue(e,t){return e.nodes.find(a=>a.id===t)}function gt(e,t){if(!t)return e.nodes.filter(r=>r.row===0);let a=ue(e,t);return a?a.connections.map(r=>ue(e,r)).filter(r=>!!r):[]}function yt(e,t=0){let a=V(e),r=ht(1,a);return{id:`run-${Date.now()}-${a}`,seed:e,act:1,map:r,currentNodeId:null,deck:Ve(),gateHp:te,gateMaxHp:te,fragments:0,potions:[null,null,null],relics:[],ascensionLevel:t,combat:null}}function Le(e,t){return{...e,deck:[...e.deck,t]}}function vt(e,t){return{...e,deck:e.deck.filter(a=>a.instanceId!==t)}}function wt(e,t){let a=ue(e.map,t);return a&&(a.visited=!0),{...e,currentNodeId:t}}function je(e,t){return{...e,gateHp:Math.min(e.gateMaxHp,e.gateHp+t)}}function bt(e,t){return e.fragments<t?null:{...e,fragments:e.fragments-t}}function qe(e,t){return{...e,fragments:e.fragments+t}}function kt(e,t){let a=e.potions.indexOf(null);if(a===-1)return null;let r=[...e.potions];return r[a]=t,{...e,potions:r}}function Tt(e){let t=e.act+1,a=V(e.seed+`-act${t}`),r=ht(t,a);return{...e,act:t,map:r,currentNodeId:null}}function xt(e){let t=[],a=de.filter(n=>n.rarity!=="common"||n.id==="slash"||n.id==="flare"),r=new Set;for(let n=0;n<5&&r.size<5;n++){let i=a[Math.floor(e()*a.length)];if(r.has(i.id)){n--;continue}r.add(i.id);let l=i.rarity==="rare"?75:i.rarity==="uncommon"?50:30;t.push({type:"card",cardDef:i,cost:l})}let o=Be[Math.floor(e()*Be.length)];return t.push({type:"potion",potionDef:o,cost:25}),t.push({type:"card_remove",cost:50}),t}var Ra=[{id:"wandering_smith",name:"The Wandering Smith",description:"A cloaked figure sits by a forge that shouldn't exist this far into the Pale. They offer to work on your defenses.",choices:[{label:"Pay 30 fragments to upgrade a random card.",effect:{type:"upgrade_random"}},{label:"Ask for supplies. (Gain 15 fragments)",effect:{type:"fragments",value:15}},{label:"Leave them be.",effect:{type:"nothing"}}]},{id:"pale_fountain",name:"The Pale Fountain",description:"A spring of luminous water bubbles from cracked stone. Its glow is unsettling but beckons.",choices:[{label:"Drink deeply. (Heal 20 HP, gain 10 max HP)",effect:{type:"heal",value:20}},{label:"Fill a flask. (Gain a random card)",effect:{type:"card_reward"}},{label:"Leave. The Pale gives nothing freely.",effect:{type:"nothing"}}]},{id:"abandoned_cache",name:"Abandoned Cache",description:"You find a sealed chest among the rubble of a collapsed watchtower.",choices:[{label:"Pry it open. (Gain 25 fragments)",effect:{type:"fragments",value:25}},{label:"Be careful \u2014 trap? (Gain 15 fragments, take 5 damage)",effect:{type:"fragments",value:15}},{label:"Leave it. Could be cursed.",effect:{type:"nothing"}}]},{id:"old_veteran",name:"The Old Veteran",description:`A scarred soldier leans against the wall. "I've seen the Pale take stronger keeps than yours," they say.`,choices:[{label:'"Teach me." (Remove a card from your deck)',effect:{type:"remove_card"}},{label:'"Give me your supplies." (Gain 20 fragments)',effect:{type:"fragments",value:20}},{label:'"Good luck." (Nothing happens)',effect:{type:"nothing"}}]},{id:"strange_merchant",name:"Strange Merchant",description:"A merchant with too many eyes offers wares from a floating pack.",choices:[{label:"Buy a rare card. (Pay 40 fragments)",effect:{type:"card_reward"}},{label:"Trade health for power. (Lose 10 HP, gain a card)",effect:{type:"damage",value:10}},{label:"Walk away.",effect:{type:"nothing"}}]}];function _t(e,t){let a=Ra;return a[Math.floor(t()*a.length)]}function It(e){switch(e){case 1:return[{templateId:"boss_suture",column:2},{templateId:"hollow",column:0},{templateId:"hollow",column:4}];case 2:return[{templateId:"boss_archivist",column:2},{templateId:"wraith",column:1},{templateId:"wraith",column:3}];case 3:return[{templateId:"boss_pale",column:2},{templateId:"echo",column:0},{templateId:"echo",column:4}];default:return[{templateId:"boss_suture",column:2}]}}var $e=2;function Ga(){return St(Ba(),".config","codekeep")}function Et(){return St(Ga(),"game.json")}function Na(){return{structures:{},npcs:[],echoes:0,highestAscension:0,totalRuns:0,totalWins:0,unlockedCardIds:[],achievements:[],narrativeFlags:[]}}function Pt(e){return{schemaVersion:$e,savedAtUnixMs:Date.now(),playerName:e,keep:Na(),activeRun:null}}function Ce(e){let t=Et(),a=Ha(t);Ct(a)||Aa(a,{recursive:!0});let r=t+".tmp",o=JSON.stringify({...e,savedAtUnixMs:Date.now()},null,2);Fe(r,o,"utf-8"),Ma(r,t)}function re(){let e=Et();if(!Ct(e))return null;let t;try{t=Da(e,"utf-8")}catch{return null}let a;try{a=JSON.parse(t)}catch{let r=e+".bak";try{Fe(r,t,"utf-8")}catch{}return process.stderr.write(`[codekeep] Corrupted save backed up to ${r}
3
- `),null}if(a.schemaVersion!==$e){let r=e+`.v${a.schemaVersion}.bak`;try{Fe(r,t,"utf-8")}catch{}return process.stderr.write(`[codekeep] Save version mismatch (got ${a.schemaVersion}, need ${$e}), backed up to ${r}
4
- `),null}return a}import Oa from"react";import{Box as Ua,Text as x}from"ink";import{writeFileSync as Wa,mkdirSync as Fa,existsSync as $a}from"fs";import{join as Rt}from"path";import{homedir as Va}from"os";var ze=Rt(Va(),".config","codekeep","crashes");function Dt(e,t){if((e instanceof Error?e.message:String(e)).includes("Raw mode is not supported"))return"";$a(ze)||Fa(ze,{recursive:!0});let r={timestamp:new Date().toISOString(),error:e instanceof Error?e.message:String(e),stack:e instanceof Error?e.stack:void 0,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch,screen:t?.screen,gameState:t?.gameState},o=`crash-${Date.now()}.json`,n=Rt(ze,o);return Wa(n,JSON.stringify(r,null,2),"utf-8"),n}function At(e){let t=encodeURIComponent(`[Crash]: ${e.error.slice(0,80)}`),a=encodeURIComponent(`## Crash Report
2
+ import{Command as Zr}from"commander";import{render as Qr}from"ink";import{useState as E,useCallback as j,useEffect as qr}from"react";import{Box as ee,Text as v,useApp as jr,useInput as zr,useStdout as Yr}from"ink";var ke=5,Te=4,We=3,et=5,he=70,xe=[{id:"strike",name:"Strike",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 6 damage to an enemy.",effects:[{type:"damage",value:6,target:"single"}]},{id:"guard",name:"Guard",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 5 Block.",effects:[{type:"block",value:5,target:"self"}]},{id:"bolster",name:"Bolster",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 8 Block.",effects:[{type:"block",value:8,target:"self"}]},{id:"spark",name:"Spark",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 4 damage to all enemies in a column.",effects:[{type:"damage",value:4,target:"column"}]},{id:"reinforce",name:"Reinforce",cost:2,type:"cast",category:"fortification",rarity:"common",description:"Gain 15 Block.",effects:[{type:"block",value:15,target:"self"}]},{id:"barrage",name:"Barrage",cost:2,type:"cast",category:"armament",rarity:"common",description:"Deal 6 damage to all enemies.",effects:[{type:"damage",value:6,target:"all"}]},{id:"mend",name:"Mend",cost:1,type:"cast",category:"edict",rarity:"common",description:"Heal 4 Gate HP.",effects:[{type:"heal",value:4,target:"self"}]},{id:"lookout",name:"Lookout",cost:0,type:"cast",category:"edict",rarity:"common",description:"Draw 1 card.",effects:[{type:"draw",value:1}]},{id:"brace",name:"Brace",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 4 Block. Draw 1 card.",effects:[{type:"block",value:4,target:"self"},{type:"draw",value:1}]},{id:"ember",name:"Ember",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 8 damage to an enemy.",effects:[{type:"damage",value:8,target:"single"}]},{id:"slash",name:"Slash",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 5 damage to an enemy. Draw 1 card.",effects:[{type:"damage",value:5,target:"single"},{type:"draw",value:1}]},{id:"rally",name:"Rally",cost:1,type:"cast",category:"edict",rarity:"common",description:"Gain 3 Block. Draw 2 cards.",effects:[{type:"block",value:3,target:"self"},{type:"draw",value:2}]},{id:"salvo",name:"Salvo",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 3 damage to all enemies.",effects:[{type:"damage",value:3,target:"all"}]},{id:"patch",name:"Patch",cost:0,type:"cast",category:"fortification",rarity:"common",description:"Gain 3 Block.",effects:[{type:"block",value:3,target:"self"}]},{id:"flare",name:"Flare",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 6 damage to all enemies in a column.",effects:[{type:"damage",value:6,target:"column"}]},{id:"cleave",name:"Cleave",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 10 damage to an enemy.",effects:[{type:"damage",value:10,target:"single"}]},{id:"iron_wall",name:"Iron Wall",cost:2,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 16 Block.",effects:[{type:"block",value:16,target:"self"}]},{id:"blitz",name:"Blitz",cost:1,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 4 damage to an enemy twice.",effects:[{type:"damage",value:4,target:"single"},{type:"damage",value:4,target:"single"}]},{id:"volley",name:"Volley",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 6 damage to all enemies.",effects:[{type:"damage",value:6,target:"all"}]},{id:"resurgence",name:"Resurgence",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Heal 8 Gate HP.",effects:[{type:"heal",value:8,target:"self"}]},{id:"keen_eye",name:"Keen Eye",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Draw 3 cards.",effects:[{type:"draw",value:3}]},{id:"expose",name:"Expose",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Vulnerable to an enemy.",effects:[{type:"vulnerable",value:2,target:"single"}]},{id:"weaken",name:"Weaken",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Weak to an enemy.",effects:[{type:"weak",value:2,target:"single"}]},{id:"bash",name:"Bash",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 7 damage. Apply 1 Vulnerable.",effects:[{type:"damage",value:7,target:"single"},{type:"vulnerable",value:1,target:"single"}]},{id:"shield_bash",name:"Shield Bash",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 6 Block. Deal 3 damage.",effects:[{type:"block",value:6,target:"self"},{type:"damage",value:3,target:"single"}]},{id:"ignite",name:"Ignite",cost:1,type:"cast",category:"armament",rarity:"common",description:"Apply 3 Burn to a column.",effects:[{type:"burn",value:3,target:"column"}]},{id:"scout",name:"Scout",cost:0,type:"cast",category:"edict",rarity:"common",description:"Draw 2 cards.",effects:[{type:"draw",value:2}]},{id:"fortify",name:"Fortify",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 10 Block.",effects:[{type:"block",value:10,target:"self"}]},{id:"sundering_strike",name:"Sundering Strike",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 12 damage. Apply 2 Vulnerable.",effects:[{type:"damage",value:12,target:"single"},{type:"vulnerable",value:2,target:"single"}]},{id:"pale_fire",name:"Pale Fire",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 5 damage to all. Apply 2 Burn.",effects:[{type:"damage",value:5,target:"all"},{type:"burn",value:2,target:"all"}]},{id:"shield_wall",name:"Shield Wall",cost:2,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 20 Block.",effects:[{type:"block",value:20,target:"self"}]},{id:"battle_cry",name:"Battle Cry",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Gain 1 Resolve. Draw 1 card.",effects:[{type:"resolve",value:1},{type:"draw",value:1}]},{id:"intimidate",name:"Intimidate",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Weak to all enemies.",effects:[{type:"weak",value:2,target:"all"}]},{id:"hemorrhage",name:"Hemorrhage",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 8 damage. Apply 4 Burn.",effects:[{type:"damage",value:8,target:"single"},{type:"burn",value:4,target:"single"}]},{id:"restoration",name:"Restoration",cost:2,type:"cast",category:"edict",rarity:"uncommon",description:"Heal 12 Gate HP. Gain 4 Block.",effects:[{type:"heal",value:12,target:"self"},{type:"block",value:4,target:"self"}]},{id:"precision",name:"Precision",cost:1,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 10 damage to an enemy. Apply 1 Vulnerable.",effects:[{type:"damage",value:10,target:"single"},{type:"vulnerable",value:1,target:"single"}]},{id:"firestorm",name:"Firestorm",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 8 damage to a column. Apply 3 Burn.",effects:[{type:"damage",value:8,target:"column"},{type:"burn",value:3,target:"column"}]},{id:"deep_guard",name:"Deep Guard",cost:1,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 7 Block. Draw 1 card.",effects:[{type:"block",value:7,target:"self"},{type:"draw",value:1}]},{id:"inferno",name:"Inferno",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 12 damage to all enemies.",effects:[{type:"damage",value:12,target:"all"}]},{id:"fortress",name:"Fortress",cost:3,type:"cast",category:"fortification",rarity:"rare",description:"Gain 25 Block. Gain 1 Resolve.",effects:[{type:"block",value:25,target:"self"},{type:"resolve",value:1}]},{id:"annihilate",name:"Annihilate",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 20 damage to an enemy.",effects:[{type:"damage",value:20,target:"single"}]},{id:"wardens_wrath",name:"Warden's Wrath",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 8 damage to all. Apply 2 Vulnerable to all.",effects:[{type:"damage",value:8,target:"all"},{type:"vulnerable",value:2,target:"all"}]},{id:"iron_bastion",name:"Iron Bastion",cost:3,type:"cast",category:"fortification",rarity:"rare",description:"Gain 30 Block. Heal 5.",effects:[{type:"block",value:30,target:"self"},{type:"heal",value:5,target:"self"}]},{id:"rally_the_keep",name:"Rally the Keep",cost:2,type:"cast",category:"edict",rarity:"rare",description:"Draw 4 cards. Gain 1 Resolve.",effects:[{type:"draw",value:4},{type:"resolve",value:1}]},{id:"conflagration",name:"Conflagration",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Apply 6 Burn to all enemies. Deal 5 damage to all.",effects:[{type:"burn",value:6,target:"all"},{type:"damage",value:5,target:"all"}]},{id:"pale_ward",name:"Pale Ward",cost:2,type:"cast",category:"fortification",rarity:"rare",description:"Gain 15 Block. Apply 2 Weak to all enemies.",effects:[{type:"block",value:15,target:"self"},{type:"weak",value:2,target:"all"}]},{id:"cataclysm",name:"Cataclysm",cost:4,type:"cast",category:"armament",rarity:"legendary",description:"Deal 30 damage to all enemies. Take 10 Gate damage.",effects:[{type:"damage",value:30,target:"all"},{type:"self_damage",value:10}]},{id:"keeps_resolve",name:"Keep's Resolve",cost:0,type:"cast",category:"edict",rarity:"legendary",description:"Gain 2 Resolve. Draw 2 cards. Gain 10 Block.",effects:[{type:"resolve",value:2},{type:"draw",value:2},{type:"block",value:10,target:"self"}]},{id:"eternal_wall",name:"Eternal Wall",cost:4,type:"cast",category:"fortification",rarity:"legendary",description:"Gain 50 Block. Heal 10 Gate HP.",effects:[{type:"block",value:50,target:"self"},{type:"heal",value:10,target:"self"}]},{id:"barricade",name:"Barricade",cost:1,type:"emplace",category:"fortification",rarity:"common",description:"Cast: Gain 4 Block. Emplace: 8 HP structure, +2 Block/turn.",effects:[{type:"block",value:4,target:"self"}],emplaceCost:1,emplaceHp:8,emplaceEffects:[{type:"block",value:2,target:"self"}]},{id:"turret",name:"Turret",cost:2,type:"emplace",category:"armament",rarity:"uncommon",description:"Cast: Deal 5 damage. Emplace: 6 HP, deals 3 damage to column/turn.",effects:[{type:"damage",value:5,target:"single"}],emplaceCost:2,emplaceHp:6,emplaceEffects:[{type:"damage",value:3,target:"column"}]},{id:"beacon",name:"Beacon",cost:1,type:"emplace",category:"edict",rarity:"uncommon",description:"Cast: Draw 1 card. Emplace: 5 HP, heals 2 Gate HP/turn.",effects:[{type:"draw",value:1}],emplaceCost:1,emplaceHp:5,emplaceEffects:[{type:"heal",value:2,target:"self"}]},{id:"ward_stone",name:"Ward Stone",cost:1,type:"emplace",category:"fortification",rarity:"common",description:"Cast: Gain 3 Block. Emplace: 10 HP, applies 1 Weak to enemies in column.",effects:[{type:"block",value:3,target:"self"}],emplaceCost:1,emplaceHp:10,emplaceEffects:[{type:"weak",value:1,target:"column"}]},{id:"siphon",name:"Siphon",cost:2,type:"emplace",category:"wild",rarity:"rare",description:"Cast: Deal 4 damage to all. Emplace: 4 HP, deals 2 damage to adjacent columns.",effects:[{type:"damage",value:4,target:"all"}],emplaceCost:2,emplaceHp:4,emplaceEffects:[{type:"damage",value:2,target:"adjacent"}]},{id:"watchtower",name:"Watchtower",cost:1,type:"emplace",category:"edict",rarity:"common",description:"Cast: Draw 2 cards. Emplace: 4 HP, applies 1 Vulnerable to enemies in column.",effects:[{type:"draw",value:2}],emplaceCost:1,emplaceHp:4,emplaceEffects:[{type:"vulnerable",value:1,target:"column"}]},{id:"flame_pit",name:"Flame Pit",cost:2,type:"emplace",category:"armament",rarity:"uncommon",description:"Cast: Deal 6 damage to column. Emplace: 5 HP, deals 4 damage to column/turn.",effects:[{type:"damage",value:6,target:"column"}],emplaceCost:2,emplaceHp:5,emplaceEffects:[{type:"damage",value:4,target:"column"}]},{id:"bulwark",name:"Bulwark",cost:2,type:"emplace",category:"fortification",rarity:"rare",description:"Cast: Gain 10 Block. Emplace: 15 HP, +3 Block/turn.",effects:[{type:"block",value:10,target:"self"}],emplaceCost:2,emplaceHp:15,emplaceEffects:[{type:"block",value:3,target:"self"}]},{id:"spike_trap",name:"Spike Trap",cost:1,type:"emplace",category:"armament",rarity:"common",description:"Cast: Deal 3 damage. Emplace: 3 HP, deals 2 damage to column/turn.",effects:[{type:"damage",value:3,target:"single"}],emplaceCost:1,emplaceHp:3,emplaceEffects:[{type:"damage",value:2,target:"column"}]},{id:"sanctum",name:"Sanctum",cost:3,type:"emplace",category:"edict",rarity:"rare",description:"Cast: Heal 6, draw 1. Emplace: 8 HP, heals 3 Gate HP/turn, +1 Block/turn.",effects:[{type:"heal",value:6,target:"self"},{type:"draw",value:1}],emplaceCost:3,emplaceHp:8,emplaceEffects:[{type:"heal",value:3,target:"self"},{type:"block",value:1,target:"self"}]}],Ht=["strike","strike","strike","guard","guard","spark","ember","brace","bolster","mend"];function $(e){return xe.find(t=>t.id===e)}var $a=[{id:"hollow",name:"Hollow",symbol:"\u2620",hp:18,damage:6,speed:1,act:1,description:"An empty shape from the Pale. Advances steadily."},{id:"needle",name:"Needle",symbol:"\u2191",hp:10,damage:4,speed:2,act:1,description:"Fast and fragile. Advances two rows per turn."},{id:"shade",name:"Shade",symbol:"\u25C8",hp:22,damage:8,speed:1,act:1,description:"A dense fragment of the Pale. Hits hard."},{id:"wisp",name:"Wisp",symbol:"\u25CB",hp:8,damage:3,speed:1,act:1,description:"Fragile but numerous. Appears in swarms."},{id:"husk",name:"Husk",symbol:"\u2593",hp:30,damage:5,speed:1,act:1,description:"Armored shell. Slow but resilient."},{id:"wraith",name:"Wraith",symbol:"\u2248",hp:15,damage:7,speed:2,act:2,description:"Drifts through emplacements. Ignores structures."},{id:"breaker",name:"Breaker",symbol:"\u2692",hp:25,damage:10,speed:1,act:2,description:"Targets emplacements first. Destroys structures."},{id:"flanker",name:"Flanker",symbol:"\u2194",hp:14,damage:6,speed:1,act:2,description:"Shifts columns before attacking."},{id:"shielder",name:"Shielder",symbol:"\u25C7",hp:20,damage:4,speed:1,act:2,description:"Grants shield to adjacent enemies."},{id:"echo",name:"Echo",symbol:"\u221E",hp:35,damage:12,speed:1,act:3,description:"A memory of something that should not exist."},{id:"boss_suture",name:"The Suture",symbol:"\u25C8",hp:60,damage:8,speed:1,act:1,description:"Stitched from fragments of the Pale. The first true threat."},{id:"boss_archivist",name:"The Archivist",symbol:"\u25A3",hp:90,damage:10,speed:1,act:2,description:"Keeper of forgotten records. Debuffs and shields methodically."},{id:"boss_pale",name:"The Pale Itself",symbol:"\u25C9",hp:130,damage:12,speed:1,act:3,description:"The void given form. Three phases of escalating horror."}];function ne(e){return $a.find(t=>t.id===e)}var _e=[{id:"heal_potion",name:"Mending Draught",description:"Heal 15 Gate HP.",effects:[{type:"heal",value:15,target:"self"}]},{id:"damage_potion",name:"Fire Flask",description:"Deal 10 damage to all enemies in a column.",effects:[{type:"damage",value:10,target:"column"}]},{id:"block_potion",name:"Iron Tonic",description:"Gain 15 Block.",effects:[{type:"block",value:15,target:"self"}]},{id:"draw_potion",name:"Seer's Ink",description:"Draw 3 cards.",effects:[{type:"draw",value:3}]},{id:"resolve_potion",name:"Warden's Flame",description:"Gain 2 Resolve this turn.",effects:[{type:"resolve",value:2}]}],$e={combat:15,elite:30,boss:50},Fa=[{npcId:"wren",tier:0,lines:[{id:"wren_t0_1",speaker:"Wren",text:"Welcome to the Keep, Warden. I maintain what's left of it."},{id:"wren_t0_2",speaker:"Wren",text:"The Pale has been encroaching for years now. Each run pushes it back \u2014 a little."},{id:"wren_t0_3",speaker:"Wren",text:"The Echoes you bring back... they're more than currency. They're fragments of what the Pale has consumed."}]},{npcId:"wren",tier:1,lines:[{id:"wren_t1_1",speaker:"Wren",text:"You're doing well, Warden. The Keep grows stronger."},{id:"wren_t1_2",speaker:"Wren",text:"I've been organizing the archives. There are records of other keeps \u2014 all fallen."},{id:"wren_t1_3",speaker:"Wren",text:"Did you know the Pale wasn't always like this? Sable might tell you more."},{id:"wren_t1_4",speaker:"Wren",text:"The structures you build... I can feel them resonate. Like the Keep remembers what it was."}]},{npcId:"wren",tier:2,lines:[{id:"wren_t2_1",speaker:"Wren",text:"I found something in the lower chambers. A journal. The previous Warden's."},{id:"wren_t2_2",speaker:"Wren",text:'"The Pale does not destroy," they wrote. "It remembers \u2014 and in remembering, unmakes."'},{id:"wren_t2_3",speaker:"Wren",text:"I think this Keep has been here longer than any of us know."}]},{npcId:"wren",tier:3,lines:[{id:"wren_t3_1",speaker:"Wren",text:"Warden. I need to tell you something. The Pale Visitor... they're not what they seem."},{id:"wren_t3_2",speaker:"Wren",text:"I've been tracking the patterns. Every fifty runs, the Pale shifts. Something deeper stirs."}]},{npcId:"wren",tier:4,lines:[{id:"wren_t4_1",speaker:"Wren",text:"The truth is... there have been many Wardens. You're not the first. And you won't be the last."},{id:"wren_t4_2",speaker:"Wren",text:"Unless you find the source. The thing the Pale is actually looking for."}]},{npcId:"sable",tier:0,lines:[{id:"sable_t0_1",speaker:"Sable",text:"Hmm? Oh, another Warden. The Archive is open, if you can read the old script."},{id:"sable_t0_2",speaker:"Sable",text:"Cards, Warden. That's what the old records call them. Patterns of power, crystallized."},{id:"sable_t0_3",speaker:"Sable",text:"The Pale erases. But patterns \u2014 true patterns \u2014 resist erasure. That's what your deck is."}]},{npcId:"sable",tier:1,lines:[{id:"sable_t1_1",speaker:"Sable",text:"I've decoded another section. The Pale isn't natural. It was created."},{id:"sable_t1_2",speaker:"Sable",text:"Someone \u2014 or something \u2014 made the Pale as a weapon. Against what, I don't yet know."},{id:"sable_t1_3",speaker:"Sable",text:"The emplacements you build... they're based on ancient designs. The old Keep had them too."}]},{npcId:"sable",tier:2,lines:[{id:"sable_t2_1",speaker:"Sable",text:'I found it. A name: "The Architect." They built the first Keep \u2014 and the first Pale.'},{id:"sable_t2_2",speaker:"Sable",text:"The Architect wanted to preserve everything. So they built a system that remembers. Perfectly."},{id:"sable_t2_3",speaker:"Sable",text:"A perfect memory is indistinguishable from a prison, Warden."}]},{npcId:"sable",tier:3,lines:[{id:"sable_t3_1",speaker:"Sable",text:"The bosses you fight... they're not monsters. They're memories. The Suture, the Archivist..."},{id:"sable_t3_2",speaker:"Sable",text:"Wait. The Archivist. That's my title. Why is there a boss named after my role?"}]},{npcId:"duskmar",tier:0,lines:[{id:"dusk_t0_1",speaker:"Duskmar",text:"I was the first to man these walls. And I'll be the last, if it comes to that."},{id:"dusk_t0_2",speaker:"Duskmar",text:"The Hollows are nothing. Wait until you face the Shades. Or the things in Act 3."},{id:"dusk_t0_3",speaker:"Duskmar",text:"Block. Always block. The Pale punishes the reckless."}]},{npcId:"duskmar",tier:1,lines:[{id:"dusk_t1_1",speaker:"Duskmar",text:"I've been teaching the walls to remember. Emplacements \u2014 that's the real defense."},{id:"dusk_t1_2",speaker:"Duskmar",text:"Every time you ascend, the Pale gets smarter. But so do you."}]},{npcId:"duskmar",tier:2,lines:[{id:"dusk_t2_1",speaker:"Duskmar",text:"I died once, you know. In the Pale. Wren brought me back with Echoes."},{id:"dusk_t2_2",speaker:"Duskmar",text:"Death isn't permanent here. That should worry you more than it comforts you."}]},{npcId:"mott",tier:0,lines:[{id:"mott_t0_1",speaker:"Mott",text:"Fragments! Echoes! Bits and pieces of a world that doesn't exist anymore!"},{id:"mott_t0_2",speaker:"Mott",text:"I trade in what the Pale leaves behind. Potions, mostly. Sometimes relics."},{id:"mott_t0_3",speaker:"Mott",text:"The shop in the Pale? That's me. Well, a memory of me. It's complicated."}]},{npcId:"mott",tier:1,lines:[{id:"mott_t1_1",speaker:"Mott",text:"Found something odd today. A card that doesn't match any pattern I've seen."},{id:"mott_t1_2",speaker:"Mott",text:"The relics aren't just powerful. They're pieces of the old world. Before the Pale."}]},{npcId:"mott",tier:2,lines:[{id:"mott_t2_1",speaker:"Mott",text:"I figured it out. The fragments? They're not resources. They're memories \u2014 compressed."},{id:"mott_t2_2",speaker:"Mott",text:"When you spend fragments, you're spending someone's past. Heavy, right?"}]},{npcId:"pale_visitor",tier:0,lines:[{id:"pv_t0_1",speaker:"???",text:"..."},{id:"pv_t0_2",speaker:"???",text:"You can see me. Interesting. Most Wardens take longer."}]},{npcId:"pale_visitor",tier:1,lines:[{id:"pv_t1_1",speaker:"The Visitor",text:"Do you know why the Keep exists, Warden?"},{id:"pv_t1_2",speaker:"The Visitor",text:"Not to fight the Pale. To contain it. There's a difference."}]},{npcId:"pale_visitor",tier:2,lines:[{id:"pv_t2_1",speaker:"The Visitor",text:"I am the Pale. Or I was. The part of it that wanted to stop."},{id:"pv_t2_2",speaker:"The Visitor",text:"Every run you complete, you weaken the whole. But you also feed it."}]},{npcId:"pale_visitor",tier:3,lines:[{id:"pv_t3_1",speaker:"The Visitor",text:"The Architect built me as a failsafe. I was supposed to shut it all down."},{id:"pv_t3_2",speaker:"The Visitor",text:"But I can't. Not alone. That's why I need a Warden."}]},{npcId:"pale_visitor",tier:4,lines:[{id:"pv_t4_1",speaker:"The Visitor",text:"There is a true ending, Warden. But it requires Ascension 15."},{id:"pv_t4_2",speaker:"The Visitor",text:"At the peak, the Pale reveals its core. And you will have a choice."},{id:"pv_t4_3",speaker:"The Visitor",text:"Destroy the Pale \u2014 and everything it remembers. Or become the new Architect."}]}];function Va(e,t){return Fa.filter(a=>a.npcId===e&&a.tier<=t).flatMap(a=>a.lines)}function Bt(e,t,r){let a=Va(e,t),o=new Set(r);return a.filter(n=>!o.has(n.id))}import{writeFileSync as nt,readFileSync as fr,mkdirSync as hr,existsSync as aa,renameSync as gr,unlinkSync as fn}from"fs";import{join as ra,dirname as yr}from"path";import{homedir as vr}from"os";function F(e){let t=e|0;return()=>{t=t+1831565813|0;let r=Math.imul(t^t>>>15,1|t);return r=r+Math.imul(r^r>>>7,61|r)^r,((r^r>>>14)>>>0)/4294967296}}function z(e){let t=0;for(let r=0;r<e.length;r++){let a=e.charCodeAt(r);t=(t<<5)-t+a|0}return t}function Nt(e,t){let r=[...e];for(let a=r.length-1;a>0;a--){let o=Math.floor(t()*(a+1));[r[a],r[o]]=[r[o],r[a]]}return r}var Oa=1;function Ve(e){return{instanceId:`card-${Oa++}`,defId:e,upgraded:!1}}function st(){return Ht.map(Ve)}function Ka(e,t){return Nt(e,t)}function Se(e,t,r,a){let o=[],n=[...e],l=[...t];for(let u=0;u<r;u++){if(n.length===0){if(l.length===0)break;n=Nt(l,a),l=[]}o.push(n.shift())}return{drawn:o,newDrawPile:n,newDiscardPile:l}}var Ua=[{templateId:"boss_suture",name:"The Suture",act:1,phases:[{hpThreshold:1,intentPattern:[{type:"attack",value:8},{type:"advance",value:1},{type:"attack",value:12}]},{hpThreshold:.5,intentPattern:[{type:"attack",value:15},{type:"summon",value:2},{type:"attack",value:10}]}],onPhaseChange:(e,t,r)=>{let a=e.columns[2];a.enemies.length<3&&a.enemies.push(it("needle",2))}},{templateId:"boss_archivist",name:"The Archivist",act:2,phases:[{hpThreshold:1,intentPattern:[{type:"debuff",value:2},{type:"attack",value:10},{type:"shield",value:15}]},{hpThreshold:.5,intentPattern:[{type:"attack",value:18},{type:"debuff",value:3},{type:"summon",value:3}]}]},{templateId:"boss_pale",name:"The Pale Itself",act:3,phases:[{hpThreshold:1,intentPattern:[{type:"attack",value:12},{type:"advance",value:1},{type:"summon",value:2}]},{hpThreshold:.6,intentPattern:[{type:"attack",value:20},{type:"debuff",value:3},{type:"attack",value:15}]},{hpThreshold:.3,intentPattern:[{type:"attack",value:25},{type:"summon",value:3},{type:"attack",value:20}]}],onPhaseChange:(e,t,r)=>{if(r>=2)for(let a of e.columns)for(let o of a.enemies){let n=o.statusEffects.find(l=>l.type==="empowered");n?n.stacks+=1:o.statusEffects.push({type:"empowered",stacks:1,duration:99})}}}];function La(e){return Ua.find(t=>t.act===e)}function qa(e,t,r){let a=e.phases[0];for(let n of e.phases)t<=n.hpThreshold&&(a=n);let o=(r-1)%a.intentPattern.length;return a.intentPattern[o]}function Wt(e){switch(e){case 1:return[{templateId:"boss_suture",column:2},{templateId:"hollow",column:0},{templateId:"hollow",column:4}];case 2:return[{templateId:"boss_archivist",column:2},{templateId:"wraith",column:1},{templateId:"wraith",column:3}];case 3:return[{templateId:"boss_pale",column:2},{templateId:"echo",column:0},{templateId:"echo",column:4}];default:return[{templateId:"boss_suture",column:2}]}}var ja=1;function it(e,t){let r=ne(e);if(!r)throw new Error(`Unknown enemy template: ${e}`);return{instanceId:`enemy-${ja++}`,templateId:e,hp:r.hp,maxHp:r.hp,column:Math.max(0,Math.min(t,ke-1)),row:0,intent:null,statusEffects:[]}}function ct(e,t,r=1){let a=ne(e.templateId);if(!a)return{type:"advance",value:1};let o=La(a.act);if(o&&o.templateId===e.templateId){let l=e.hp/e.maxHp;return qa(o,l,r)}let n=t();switch(e.templateId){case"shielder":return n<.3?{type:"shield",value:5}:n<.6?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column};case"flanker":{if(n<.4){let l=t()<.5?-1:1,u=Math.max(0,Math.min(ke-1,e.column+l));return e.column=u,{type:"attack",value:a.damage,targetColumn:u}}return n<.7?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column}}case"breaker":return n<.6?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column};case"wraith":return n<.4?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column};case"echo":return n<.3?{type:"buff",value:1}:n<.6?{type:"attack",value:a.damage,targetColumn:e.column}:{type:"advance",value:a.speed};default:return n<.5?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column}}}function za(e,t,r){return r<0||r>=e.columns.length||e.columns[r].emplacement||!t.emplaceHp||!t.emplaceEffects?!1:(e.columns[r].emplacement={cardDefId:t.id,hp:t.emplaceHp,maxHp:t.emplaceHp,effects:t.emplaceEffects},e.events.push({type:"emplacement_placed",turn:e.turn,data:{cardId:t.id,column:r}}),!0)}function Ya(e){for(let t of e.columns)if(t.emplacement){for(let r of t.emplacement.effects)Xa(e,t.index,r);e.events.push({type:"emplacement_triggered",turn:e.turn,data:{column:t.index,cardId:t.emplacement.cardDefId}})}}function Xa(e,t,r){let a=e.columns[t];switch(r.type){case"damage":{if(r.target==="column")for(let o of a.enemies)o.hp-=r.value;else if(r.target==="adjacent")for(let o=Math.max(0,t-1);o<=Math.min(e.columns.length-1,t+1);o++)for(let n of e.columns[o].enemies)n.hp-=r.value;else for(let o of a.enemies)o.hp-=r.value;break}case"block":e.gateBlock+=r.value;break;case"heal":e.gateHp=Math.min(e.gateMaxHp,e.gateHp+r.value);break;case"weak":for(let o of a.enemies){let n=o.statusEffects.find(l=>l.type==="weak");n?n.stacks+=r.value:o.statusEffects.push({type:"weak",stacks:r.value,duration:2})}break;case"vulnerable":for(let o of a.enemies){let n=o.statusEffects.find(l=>l.type==="vulnerable");n?n.stacks+=r.value:o.statusEffects.push({type:"vulnerable",stacks:r.value,duration:2})}break}}function Ja(e){e.statusEffects=e.statusEffects.map(t=>({...t,duration:t.duration-1})).filter(t=>t.duration>0&&t.stacks>0)}function ge(e,t,r,a=2){let o=e.statusEffects.find(n=>n.type===t);o?(o.stacks+=r,o.duration=Math.max(o.duration,a)):e.statusEffects.push({type:t,stacks:r,duration:a})}function Ie(e,t){return e.statusEffects.find(a=>a.type===t)?.stacks??0}function Za(e){let t=1,r=Ie(e,"vulnerable");return r>0&&(t*=1+r*.25),t}function Qa(e){let t=1,r=Ie(e,"weak");return r>0&&(t*=Math.max(.25,1-r*.15)),Ie(e,"empowered")>0&&(t*=1+Ie(e,"empowered")*.25),t}function er(e){let t=Ie(e,"burn");if(t>0){e.hp-=t;let r=e.statusEffects.find(a=>a.type==="burn");return r&&(r.stacks=Math.max(0,r.stacks-1)),t}return 0}function $t(e,t,r=he,a=he,o=[{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"needle",column:2}]){let n=F(t),l=Ka(e,n),u=Array.from({length:ke},(k,_)=>({index:_,enemies:[],emplacement:null}));for(let k of o){let _=it(k.templateId,k.column);u[_.column].enemies.push(_)}let{drawn:s,newDrawPile:p,newDiscardPile:g}=Se(l,[],et,n),f={columns:u,hand:s,drawPile:p,discardPile:g,exhaustPile:[],gateHp:r,gateMaxHp:a,gateBlock:0,resolve:We,maxResolve:We,turn:1,phase:"player",outcome:"undecided",events:[],seed:t};for(let k of u)for(let _ of k.enemies)_.intent=ct(_,n,1);return A(f,"turn_start",{turn:1}),f}function A(e,t,r){e.events.push({type:t,turn:e.turn,data:r})}function Ft(e,t,r,a=!1){if(e.phase!=="player")return e;let o=e.hand[t];if(!o)return e;let n=$(o.defId);if(!n)return e;if(a&&n.type==="emplace"&&n.emplaceCost!==void 0){if(n.emplaceCost>e.resolve)return e;e.resolve-=n.emplaceCost,e.hand.splice(t,1),za(e,n,r??0),e.exhaustPile.push(o)}else{if(n.cost>e.resolve)return e;e.resolve-=n.cost,e.hand.splice(t,1);for(let l of n.effects)tr(e,l,r??0);e.discardPile.push(o)}return A(e,"card_played",{cardId:o.defId,targetColumn:r,asEmplace:a}),rt(e),e}function tr(e,t,r){switch(t.type){case"damage":{if(t.target==="all")for(let a of e.columns)for(let o of a.enemies)tt(e,o,t.value);else if(t.target==="column"){let a=e.columns[r];if(a)for(let o of a.enemies)tt(e,o,t.value)}else{let a=e.columns[r];if(a&&a.enemies.length>0){let o=a.enemies.reduce((n,l)=>n.row>=l.row?n:l);tt(e,o,t.value)}}at(e);break}case"block":e.gateBlock+=t.value,A(e,"block_gained",{value:t.value});break;case"heal":e.gateHp=Math.min(e.gateMaxHp,e.gateHp+t.value);break;case"draw":{let{drawn:a,newDrawPile:o,newDiscardPile:n}=Se(e.drawPile,e.discardPile,t.value,F(e.seed+e.turn));e.hand.push(...a),e.drawPile=o,e.discardPile=n;break}case"resolve":e.resolve=Math.min(e.maxResolve+5,e.resolve+t.value);break;case"vulnerable":{let a=e.columns[r];if(a){for(let o of a.enemies)ge(o,"vulnerable",t.value,3);A(e,"status_applied",{type:"vulnerable",value:t.value,column:r})}break}case"weak":{let a=e.columns[r];if(a){for(let o of a.enemies)ge(o,"weak",t.value,3);A(e,"status_applied",{type:"weak",value:t.value,column:r})}break}case"burn":{if(t.target==="all")for(let a of e.columns)for(let o of a.enemies)ge(o,"burn",t.value,99);else{let a=e.columns[r];if(a)for(let o of a.enemies)ge(o,"burn",t.value,99)}break}case"self_damage":{e.gateHp-=t.value,A(e,"gate_hit",{self:!0,damage:t.value});break}}}function tt(e,t,r){let a=Za(t),o=Math.floor(r*a);t.hp-=o,A(e,"damage_dealt",{targetId:t.instanceId,damage:o})}function at(e){for(let t of e.columns)t.enemies=t.enemies.filter(r=>r.hp<=0?(A(e,"enemy_killed",{enemyId:r.instanceId,templateId:r.templateId}),!1):!0)}function Vt(e){return e.phase!=="player"||(A(e,"turn_end",{turn:e.turn}),e.phase="enemy",ar(e)),e}function ar(e){let t=F(e.seed+e.turn*31);for(let n of e.columns)for(let l of n.enemies){if(l.hp<=0)continue;let u=l.intent??{type:"advance",value:1};rr(e,l,u)}if(at(e),rt(e),e.outcome!=="undecided"||(e.turn++,e.phase="player",e.resolve=We,e.gateBlock=0,Ya(e),at(e),rt(e),e.outcome!=="undecided"))return;e.discardPile.push(...e.hand),e.hand=[];let{drawn:r,newDrawPile:a,newDiscardPile:o}=Se(e.drawPile,e.discardPile,et,t);e.hand=r,e.drawPile=a,e.discardPile=o;for(let n of e.columns)for(let l of n.enemies)l.intent=ct(l,t,e.turn);A(e,"turn_start",{turn:e.turn})}function rr(e,t,r){let a=ne(t.templateId),o=Qa(t);if(er(t),Ja(t),!(t.hp<=0))switch(r.type){case"advance":if(t.row=Math.min(Te-1,t.row+(a?.speed??1)),A(e,"enemy_advance",{enemyId:t.instanceId,row:t.row}),t.row>=Te-1){let n=e.columns[t.column];if(n.emplacement)n.emplacement.hp-=a?.damage??4,n.emplacement.hp<=0&&(A(e,"emplacement_destroyed",{column:t.column}),n.emplacement=null);else{let l=Math.floor((a?.damage??4)*o),u=Math.min(e.gateBlock,l);e.gateBlock-=u,e.gateHp-=l-u,A(e,"gate_hit",{enemyId:t.instanceId,damage:l-u,blocked:u})}}break;case"attack":{let n=Math.floor((a?.damage??4)*o),l=Math.min(e.gateBlock,n);e.gateBlock-=l,e.gateHp-=n-l,A(e,"gate_hit",{enemyId:t.instanceId,damage:n-l,blocked:l});break}case"buff":ge(t,"empowered",r.value,3);break;case"debuff":{e.gateBlock=Math.max(0,e.gateBlock-r.value*2);break}case"shield":{let n=e.columns[t.column];for(let l of n.enemies)ge(l,"fortified",r.value,2);break}case"summon":{let n=e.columns.findIndex(l=>l.enemies.length===0);if(n>=0){let l=it("wisp",n);e.columns[n].enemies.push(l),l.intent=ct(l,F(e.seed+e.turn*97))}break}}}function rt(e){if(e.gateHp<=0){e.gateHp=0,e.outcome="lose",e.phase="ended",A(e,"combat_end",{outcome:"lose"});return}e.columns.reduce((r,a)=>r+a.enemies.length,0)===0&&(e.outcome="win",e.phase="ended",A(e,"combat_end",{outcome:"win"}))}function lt(e,t=3,r=[]){let a=new Set(["strike","guard","bolster","brace","mend",...r]),n=xe.filter(p=>!a.has(p.id)).map(p=>({card:p,weight:p.rarity==="common"?6:p.rarity==="uncommon"?3:1})),l=n.reduce((p,g)=>p+g.weight,0),u=[],s=new Set;for(let p=0;p<t&&n.length>0;p++){let g=e()*l;for(let f of n)if(!s.has(f.card.id)&&(g-=f.weight,g<=0)){u.push(f.card),s.add(f.card.id);break}if(u.length<=p){let f=n.filter(k=>!s.has(k.card.id));if(f.length>0){let k=f[Math.floor(e()*f.length)];u.push(k.card),s.add(k.card.id)}}}return u}var nr=[{name:"Scout Party",enemies:[{templateId:"hollow",column:1},{templateId:"hollow",column:3}]},{name:"Swift Assault",enemies:[{templateId:"needle",column:0},{templateId:"needle",column:2},{templateId:"needle",column:4}]},{name:"Pale Vanguard",enemies:[{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"needle",column:2}]},{name:"Wisp Swarm",enemies:[{templateId:"wisp",column:0},{templateId:"wisp",column:1},{templateId:"wisp",column:2},{templateId:"wisp",column:3},{templateId:"wisp",column:4}]},{name:"Heavy Patrol",enemies:[{templateId:"shade",column:1},{templateId:"hollow",column:3}]},{name:"Armored Column",enemies:[{templateId:"husk",column:2},{templateId:"wisp",column:0},{templateId:"wisp",column:4}]}],or=[{name:"The Dark Tide",enemies:[{templateId:"shade",column:1},{templateId:"shade",column:3},{templateId:"needle",column:2}]},{name:"Hollow Legion",enemies:[{templateId:"hollow",column:0},{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"hollow",column:4}]}],sr=[{name:"The Warband",enemies:[{templateId:"breaker",column:2},{templateId:"shielder",column:1},{templateId:"shielder",column:3}]},{name:"Flanking Ambush",enemies:[{templateId:"flanker",column:0},{templateId:"flanker",column:4},{templateId:"wraith",column:2}]}],ir=[{name:"Echo Vanguard",enemies:[{templateId:"echo",column:1},{templateId:"echo",column:3}]},{name:"The Final Test",enemies:[{templateId:"echo",column:2},{templateId:"breaker",column:0},{templateId:"flanker",column:4}]}],cr=[{name:"Wraith Drift",enemies:[{templateId:"wraith",column:1},{templateId:"wraith",column:3}]},{name:"Breach Team",enemies:[{templateId:"breaker",column:2},{templateId:"flanker",column:0},{templateId:"flanker",column:4}]},{name:"Shield Wall",enemies:[{templateId:"shielder",column:2},{templateId:"hollow",column:1},{templateId:"hollow",column:3}]}],lr=[{name:"Echoes of the End",enemies:[{templateId:"echo",column:1},{templateId:"echo",column:3}]},{name:"Final Surge",enemies:[{templateId:"echo",column:2},{templateId:"wraith",column:0},{templateId:"wraith",column:4},{templateId:"breaker",column:1}]}];function Ot(e,t,r=!1){let a;if(r)switch(e){case 2:a=sr;break;case 3:a=ir;break;default:a=or;break}else switch(e){case 2:a=cr;break;case 3:a=lr;break;default:a=nr;break}let o=Math.floor(t()*a.length);return{...a[o],isElite:r}}var Fe=12,Gt=4;function dr(e,t,r){if(e===0)return"combat";if(e===t-1)return"boss";let a=r();return e===Math.floor(t/2)?a<.5?"rest":"shop":a<.5?"combat":a<.65?"elite":a<.78?"event":a<.88?"rest":"shop"}function Kt(e,t){let r=F(t),a=[],o=[];for(let n=0;n<Fe;n++){let l=[],u=n===0||n===Fe-1?1:Math.floor(r()*2)+2,s=new Set;for(;s.size<u;)s.add(Math.floor(r()*Gt));for(let p=0;p<Gt;p++)if(s.has(p)){let g=dr(n,Fe,r),f={id:`node-${e}-${n}-${p}`,type:g,row:n,column:p,connections:[],visited:!1};l.push(f),a.push(f)}else l.push(null);o.push(l)}for(let n=0;n<Fe-1;n++){let l=o[n].filter(s=>s!==null),u=o[n+1].filter(s=>s!==null);if(u.length!==0){for(let s of l){let p=u.map(g=>({node:g,dist:Math.abs(g.column-s.column)})).sort((g,f)=>g.dist-f.dist);s.connections.push(p[0].node.id),p.length>1&&r()<.4&&s.connections.push(p[1].node.id)}for(let s of u)!l.some(g=>g.connections.includes(s.id))&&l.length>0&&l.map(f=>({node:f,dist:Math.abs(f.column-s.column)})).sort((f,k)=>f.dist-k.dist)[0].node.connections.push(s.id)}}return{act:e,nodes:a}}function Ce(e,t){return e.nodes.find(r=>r.id===t)}function Ut(e,t){if(!t)return e.nodes.filter(a=>a.row===0);let r=Ce(e,t);return r?r.connections.map(a=>Ce(e,a)).filter(a=>!!a):[]}function Lt(e,t=0){let r=z(e),a=Kt(1,r);return{id:`run-${Date.now()}-${r}`,seed:e,act:1,map:a,currentNodeId:null,deck:st(),gateHp:he,gateMaxHp:he,fragments:0,potions:[null,null,null],relics:[],ascensionLevel:t,combat:null}}function dt(e,t){return{...e,deck:[...e.deck,t]}}function mt(e,t){return{...e,deck:e.deck.filter(r=>r.instanceId!==t)}}function qt(e,t){let r=Ce(e.map,t);return r&&(r.visited=!0),{...e,currentNodeId:t}}function ut(e,t){return{...e,gateHp:Math.min(e.gateMaxHp,e.gateHp+t)}}function jt(e,t){return e.fragments<t?null:{...e,fragments:e.fragments-t}}function Oe(e,t){return{...e,fragments:e.fragments+t}}function zt(e,t){let r=e.potions.indexOf(null);if(r===-1)return null;let a=[...e.potions];return a[r]=t,{...e,potions:a}}function Yt(e,t){if(t<0||t>=e.potions.length)return null;let r=e.potions[t];if(!r)return null;let a=[...e.potions];return a[t]=null,{run:{...e,potions:a},potionId:r}}function Xt(e){let t=e.act+1,r=z(e.seed+`-act${t}`),a=Kt(t,r);return{...e,act:t,map:a,currentNodeId:null}}function Jt(e){let t=[],r=xe.filter(n=>n.rarity!=="common"||n.id==="slash"||n.id==="flare"),a=new Set;for(let n=0;n<5&&a.size<5;n++){let l=r[Math.floor(e()*r.length)];if(a.has(l.id)){n--;continue}a.add(l.id);let u=l.rarity==="rare"?75:l.rarity==="uncommon"?50:30;t.push({type:"card",cardDef:l,cost:u})}let o=_e[Math.floor(e()*_e.length)];return t.push({type:"potion",potionDef:o,cost:25}),t.push({type:"card_remove",cost:50}),t}var mr=[{id:"wandering_smith",name:"The Wandering Smith",description:"A cloaked figure sits by a forge that shouldn't exist this far into the Pale. They offer to work on your defenses.",choices:[{label:"Ask them to reinforce the gate. (Heal 15 HP)",effect:{type:"heal",value:15}},{label:"Ask for supplies. (Gain 15 fragments)",effect:{type:"fragments",value:15}},{label:"Leave them be.",effect:{type:"nothing"}}]},{id:"pale_fountain",name:"The Pale Fountain",description:"A spring of luminous water bubbles from cracked stone. Its glow is unsettling but beckons.",choices:[{label:"Drink deeply. (+10 max Gate HP)",effect:{type:"max_hp",value:10}},{label:"Fill a flask. (Gain a random card)",effect:{type:"card_reward"}},{label:"Leave. The Pale gives nothing freely.",effect:{type:"nothing"}}]},{id:"abandoned_cache",name:"Abandoned Cache",description:"You find a sealed chest among the rubble of a collapsed watchtower.",choices:[{label:"Pry it open. (Gain 25 fragments)",effect:{type:"fragments",value:25}},{label:"Smash it quickly. (Gain 10 fragments)",effect:{type:"fragments",value:10}},{label:"Leave it. Could be cursed.",effect:{type:"nothing"}}]},{id:"old_veteran",name:"The Old Veteran",description:`A scarred soldier leans against the wall. "I've seen the Pale take stronger keeps than yours," they say.`,choices:[{label:'"Teach me." (Remove a card from your deck)',effect:{type:"remove_card"}},{label:'"Give me your supplies." (Gain 20 fragments)',effect:{type:"fragments",value:20}},{label:'"Good luck." (Nothing happens)',effect:{type:"nothing"}}]},{id:"strange_merchant",name:"Strange Merchant",description:"A merchant with too many eyes offers wares from a floating pack.",choices:[{label:"Browse their cards. (Gain a card)",effect:{type:"card_reward"}},{label:"Sell your blood. (Lose 10 HP, gain 30 fragments)",effect:{type:"damage",value:10}},{label:"Walk away.",effect:{type:"nothing"}}]}],ur=[{id:"pale_scholar",name:"The Pale Scholar",description:'An ancient figure pores over a tome that seems to read itself. "The Pale remembers everything," they whisper.',choices:[{label:"Ask for knowledge. (Gain a card)",effect:{type:"card_reward"}},{label:"Ask for healing. (Heal 20 HP)",effect:{type:"heal",value:20}},{label:"Back away slowly.",effect:{type:"nothing"}}]},{id:"fractured_mirror",name:"The Fractured Mirror",description:"A mirror stands in the corridor, cracked but gleaming. Your reflection looks stronger than you feel.",choices:[{label:"Reach through. (+5 max Gate HP, lose 5 HP)",effect:{type:"max_hp",value:5}},{label:"Smash it. (Gain 20 fragments)",effect:{type:"fragments",value:20}},{label:"Walk past. Mirrors lie.",effect:{type:"nothing"}}]},{id:"deserters_cache",name:"Deserter's Cache",description:"Behind a collapsed wall, you find supplies left by someone who fled the Keep long ago.",choices:[{label:"Take everything. (Gain 30 fragments)",effect:{type:"fragments",value:30}},{label:"Take only what you need. (Heal 10 HP, gain 10 fragments)",effect:{type:"heal",value:10}},{label:"Leave it for the next warden.",effect:{type:"nothing"}}]}],pr=[{id:"the_last_wall",name:"The Last Wall",description:"The final barrier before the Pale's heart. Ancient wards still flicker across its surface.",choices:[{label:"Channel the wards. (+15 max Gate HP)",effect:{type:"max_hp",value:15}},{label:"Break through quickly. (Gain 20 fragments)",effect:{type:"fragments",value:20}},{label:"Let it stand. Its purpose is not yours.",effect:{type:"nothing"}}]},{id:"echo_of_a_warden",name:"Echo of a Warden",description:`A ghostly figure in warden's garb appears. "I held this keep once. Take what remains of my strength."`,choices:[{label:"Accept their gift. (Gain a card, lose 5 HP)",effect:{type:"card_reward"}},{label:"Ask them to strengthen the gate. (Heal 25 HP)",effect:{type:"heal",value:25}},{label:'"Rest now, Warden."',effect:{type:"nothing"}}]},{id:"pale_bargain",name:"The Pale Bargain",description:'The void itself speaks: "Give me something, and I will give you power."',choices:[{label:"Sacrifice a card. (Remove a card)",effect:{type:"remove_card"}},{label:"Sacrifice blood. (Lose 15 HP, gain 40 fragments)",effect:{type:"damage",value:15}},{label:'"I make no deals with the void."',effect:{type:"nothing"}}]}];function Zt(e,t){let r;switch(e){case 2:r=ur;break;case 3:r=pr;break;default:r=mr;break}return r[Math.floor(t()*r.length)]}var q=[{id:"forge",name:"The Forge",description:"Unlock card upgrades at rest sites. Higher levels improve upgrades.",maxLevel:3,upgradeCost:e=>e*30},{id:"archive",name:"The Archive",description:"Unlock rare cards in the reward pool. Higher levels add legendaries.",maxLevel:3,upgradeCost:e=>e*40},{id:"beacon_tower",name:"Beacon Tower",description:"+1 starting hand size per level.",maxLevel:2,upgradeCost:e=>e*50},{id:"foundry",name:"The Foundry",description:"Emplacements start with +2 HP per level.",maxLevel:3,upgradeCost:e=>e*25},{id:"sanctum_hall",name:"Sanctum Hall",description:"+5 max Gate HP per level.",maxLevel:3,upgradeCost:e=>e*35}];function Ee(e,t){return e.structures[t]??0}function Qt(e,t){let r=q.find(n=>n.id===t);if(!r)return null;let a=Ee(e,t);if(a>=r.maxLevel)return null;let o=r.upgradeCost(a+1);return e.echoes<o?null:{...e,echoes:e.echoes-o,structures:{...e.structures,[t]:a+1}}}function pt(){return[{id:"wren",tier:0,echoesGiven:0,dialoguesSeen:[]},{id:"sable",tier:0,echoesGiven:0,dialoguesSeen:[]},{id:"duskmar",tier:0,echoesGiven:0,dialoguesSeen:[]},{id:"mott",tier:0,echoesGiven:0,dialoguesSeen:[]},{id:"pale_visitor",tier:0,echoesGiven:0,dialoguesSeen:[]}]}function ft(e,t,r){return(e?10+t*5:3)+Math.floor(r*.5)}function ea(e,t){let r=t.npcs.find(o=>o.id===e);if(!r)return null;let a=Bt(e,r.tier,r.dialoguesSeen);return a.length===0?null:{speaker:a[0].speaker,text:a[0].text,dialogueId:a[0].id}}function ta(e,t,r){let a=e.npcs.findIndex(l=>l.id===t);if(a===-1)return e;let o=[...e.npcs],n={...o[a]};return n.dialoguesSeen=[...n.dialoguesSeen,r],o[a]=n,{...e,npcs:o}}var ot=2;function wr(){return ra(vr(),".config","codekeep")}function na(){return ra(wr(),"game.json")}function br(){return{structures:{},npcs:[],echoes:0,highestAscension:0,totalRuns:0,totalWins:0,unlockedCardIds:[],achievements:[],narrativeFlags:[]}}function oa(e){return{schemaVersion:ot,savedAtUnixMs:Date.now(),playerName:e,keep:br(),activeRun:null}}function Y(e){let t=na(),r=yr(t);aa(r)||hr(r,{recursive:!0});let a=t+".tmp",o=JSON.stringify({...e,savedAtUnixMs:Date.now()},null,2);nt(a,o,"utf-8"),gr(a,t)}function ye(){let e=na();if(!aa(e))return null;let t;try{t=fr(e,"utf-8")}catch{return null}let r;try{r=JSON.parse(t)}catch{let a=e+".bak";try{nt(a,t,"utf-8")}catch{}return process.stderr.write(`[codekeep] Corrupted save backed up to ${a}
3
+ `),null}if(r.schemaVersion!==ot){let a=e+`.v${r.schemaVersion}.bak`;try{nt(a,t,"utf-8")}catch{}return process.stderr.write(`[codekeep] Save version mismatch (got ${r.schemaVersion}, need ${ot}), backed up to ${a}
4
+ `),null}return r}import Ir from"react";import{Box as Cr,Text as S}from"ink";import{writeFileSync as kr,mkdirSync as Tr,existsSync as xr}from"fs";import{join as sa}from"path";import{homedir as _r}from"os";var ht=sa(_r(),".config","codekeep","crashes");function ia(e,t){if((e instanceof Error?e.message:String(e)).includes("Raw mode is not supported"))return"";xr(ht)||Tr(ht,{recursive:!0});let a={timestamp:new Date().toISOString(),error:e instanceof Error?e.message:String(e),stack:e instanceof Error?e.stack:void 0,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch,screen:t?.screen,gameState:t?.gameState},o=`crash-${Date.now()}.json`,n=sa(ht,o);return kr(n,JSON.stringify(a,null,2),"utf-8"),n}function ca(e){let t=encodeURIComponent(`[Crash]: ${e.error.slice(0,80)}`),r=encodeURIComponent(`## Crash Report
5
5
 
6
6
  **Error:** ${e.error}
7
7
 
@@ -16,8 +16,8 @@ ${e.stack??"N/A"}
16
16
  - OS: ${e.platform} ${e.arch}
17
17
  - Screen: ${e.screen??"unknown"}
18
18
  - Time: ${e.timestamp}
19
- `);return`https://github.com/tooyipjee/codekeep/issues/new?title=${t}&body=${a}&labels=bug,crash`}import{Fragment as Mt,jsx as D,jsxs as q}from"react/jsx-runtime";var Se=class extends Oa.Component{constructor(t){super(t),this.state={error:null,crashFilePath:null,issueUrl:null}}static getDerivedStateFromError(t){return{error:t}}componentDidCatch(t){try{let a=Dt(t),r={timestamp:new Date().toISOString(),error:t.message,stack:t.stack,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch},o=At(r);this.setState({crashFilePath:a,issueUrl:o})}catch{}}render(){return this.state.error?q(Ua,{flexDirection:"column",padding:1,children:[D(x,{bold:!0,color:"red",children:"\u25C6 CodeKeep \u2014 Something went wrong"}),D(x,{children:" "}),D(x,{color:"red",children:this.state.error.message}),D(x,{children:" "}),D(x,{dimColor:!0,children:"Your save file is at:"}),q(x,{children:[" ","~/.config/codekeep/game.json"]}),D(x,{children:" "}),this.state.crashFilePath&&q(Mt,{children:[D(x,{dimColor:!0,children:"Crash report saved to:"}),q(x,{children:[" ",this.state.crashFilePath]}),D(x,{children:" "})]}),this.state.issueUrl&&q(Mt,{children:[D(x,{dimColor:!0,children:"Report this bug:"}),q(x,{color:"cyan",children:[" ",this.state.issueUrl]}),D(x,{children:" "})]}),D(x,{dimColor:!0,children:"If the game won't start, try:"}),q(x,{bold:!0,children:[" ","codekeep --tutorial"]}),D(x,{children:" "}),D(x,{dimColor:!0,children:"Press Ctrl+C to exit"})]}):this.props.children}};import{Box as Ye,Text as A}from"ink";import{Box as Ka,Text as U}from"ink";import{jsx as pe,jsxs as Ee}from"react/jsx-runtime";function La(e){if(!e)return"";switch(e.type){case"advance":return`\u2193${e.value}`;case"attack":return`\u2694${e.value}`;case"buff":return`\u25B2${e.value}`;case"debuff":return`\u25BC${e.value}`;case"shield":return`\u25C8${e.value}`;case"summon":return`+${e.value}`;default:return""}}function ja(e,t,a=6){let r=Math.max(0,Math.round(e/t*a));return"\u2588".repeat(r)+"\u2591".repeat(a-r)}function qa(e){return`${j(e.templateId)?.symbol??"?"}${e.hp}`}function Ht({columns:e,targetColumn:t,showTarget:a,gateHp:r,gateMaxHp:o,gateBlock:n}){let l=e.map((p,y)=>{let k=`Col ${y+1}`,H=a&&y===t,P=k.padStart(Math.floor((12+k.length)/2)).padEnd(12);return H?`[${P.slice(1,-1)}]`:` ${P.slice(1,-1)} `}).join("\u2502"),s=[];for(let p=0;p<ee;p++){let y=e.map(k=>{let H=k.enemies.filter(R=>R.row===p);return H.length===0?k.emplacement&&p===ee-1?"[EMPL]".padStart(Math.floor(9)).padEnd(12):"\xB7".padStart(Math.floor(6.5)).padEnd(12):H.map(R=>{let F=La(R.intent);return`${qa(R)}${F?" "+F:""}`}).join(" ").slice(0,12).padEnd(12)}).join("\u2502");s.push(y)}let u=Math.round(r/o*100),f=u>60?"green":u>30?"yellow":"red";return Ee(Ka,{flexDirection:"column",children:[pe(U,{bold:!0,dimColor:!0,children:"\u2500".repeat(64)}),pe(U,{bold:!0,children:l}),pe(U,{dimColor:!0,children:"\u2500".repeat(64)}),s.map((p,y)=>pe(U,{children:p},y)),pe(U,{dimColor:!0,children:"\u2550".repeat(64)}),Ee(U,{children:[Ee(U,{bold:!0,color:f,children:["Gate ",ja(r,o,12)," ",r,"/",o]}),n>0&&Ee(U,{color:"cyan",children:[" \u{1F6E1}",n]})]})]})}import{Box as za,Text as ne}from"ink";import{jsx as Bt,jsxs as fe}from"react/jsx-runtime";function Ya(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function Xa(e){switch(e){case"armament":return"\u2694";case"fortification":return"\u{1F6E1}";case"edict":return"\u2726";case"wild":return"\u25C8";default:return"\xB7"}}function Gt({hand:e,selectedIndex:t,resolve:a}){return e.length===0?Bt(ne,{dimColor:!0,children:"No cards in hand."}):fe(za,{flexDirection:"column",children:[Bt(ne,{bold:!0,children:"Hand:"}),e.map((r,o)=>{let n=G(r.defId);if(!n)return null;let i=o===t,l=n.cost<=a,s=i?"\u25B6 ":" ",u=`${o+1}`,f=`[${n.cost}]`;return fe(ne,{children:[fe(ne,{bold:i,color:i?"yellow":void 0,children:[s,u,"."," "]}),fe(ne,{color:l?Ya(n.rarity):"gray",bold:i,children:[Xa(n.category)," ",n.name," ",f]}),i&&fe(ne,{dimColor:!0,children:[" \u2014 ",n.description]})]},r.instanceId)})]})}import{jsx as M,jsxs as he}from"react/jsx-runtime";function Nt({combat:e,selectedCard:t,targetColumn:a,needsTarget:r,message:o}){let n=e.columns.reduce((i,l)=>i+l.enemies.length,0);return he(Ye,{flexDirection:"column",children:[he(Ye,{justifyContent:"space-between",children:[M(A,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),he(A,{children:["Turn ",M(A,{bold:!0,children:e.turn})," ","Resolve ",he(A,{bold:!0,color:"cyan",children:[e.resolve,"/",e.maxResolve]})," ","Enemies ",M(A,{bold:!0,color:"red",children:n})," ","Draw ",M(A,{dimColor:!0,children:e.drawPile.length})," ","Discard ",M(A,{dimColor:!0,children:e.discardPile.length})]})]}),M(A,{children:" "}),M(Ht,{columns:e.columns,targetColumn:a,showTarget:r&&t>=0,gateHp:e.gateHp,gateMaxHp:e.gateMaxHp,gateBlock:e.gateBlock}),M(A,{children:" "}),M(Gt,{hand:e.hand,selectedIndex:t,resolve:e.resolve}),M(A,{children:" "}),o&&M(A,{color:"yellow",children:o}),e.phase==="player"&&he(A,{dimColor:!0,children:["1-",e.hand.length," select card \u2190\u2192 target column Enter play Space end turn q quit"]}),e.outcome!=="undecided"&&M(Ye,{marginTop:1,children:M(A,{bold:!0,color:e.outcome==="win"?"green":"red",children:e.outcome==="win"?"\u2605 VICTORY \u2014 The siege is broken!":"\u2717 DEFEAT \u2014 The Gate has fallen."})})]})}import{Box as Wt,Text as K}from"ink";import{jsx as oe,jsxs as Pe}from"react/jsx-runtime";function Ja(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function Ft({cards:e,selectedIndex:t}){return Pe(Wt,{flexDirection:"column",padding:1,children:[oe(K,{bold:!0,color:"yellow",children:"\u25C6 Choose a Card Reward"}),oe(K,{children:" "}),e.map((a,r)=>{let o=r===t;return Pe(Wt,{flexDirection:"column",marginBottom:1,children:[Pe(K,{bold:o,color:o?"yellow":Ja(a.rarity),children:[o?"\u25B6 ":" ",r+1,". ",a.name," [",a.cost,"] (",a.rarity,")"]}),Pe(K,{dimColor:!0,children:[" ",a.description]})]},a.id)}),oe(K,{children:" "}),oe(K,{dimColor:!0,children:t>=0?` ${e.length+1}. Skip \u2014 take no card`:`\u25B6 ${e.length+1}. Skip \u2014 take no card`}),oe(K,{children:" "}),oe(K,{dimColor:!0,children:"\u2191\u2193 navigate Enter select s skip"})]})}import{Box as Za,Text as ge}from"ink";import{jsx as Je,jsxs as Xe}from"react/jsx-runtime";function Qa(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function $t({deck:e}){let t=new Map;for(let r of e)t.set(r.defId,(t.get(r.defId)??0)+1);let a=[...t.entries()].map(([r,o])=>({def:G(r),count:o})).filter(r=>r.def).sort((r,o)=>{let n={common:0,uncommon:1,rare:2,legendary:3},i=n[r.def.rarity]??0,l=n[o.def.rarity]??0;return i!==l?i-l:r.def.name.localeCompare(o.def.name)});return Xe(Za,{flexDirection:"column",padding:1,children:[Xe(ge,{bold:!0,color:"yellow",children:["\u25C6 Your Deck"," (",e.length," cards)"]}),Je(ge,{children:" "}),a.map(({def:r,count:o})=>Xe(ge,{color:Qa(r.rarity),children:[o>1?`${o}x `:" ",r.name," [",r.cost,"] \u2014 ",r.description]},r.id)),Je(ge,{children:" "}),Je(ge,{dimColor:!0,children:"Press q or Esc to go back."})]})}import{Box as Ze,Text as L}from"ink";import{jsx as z,jsxs as Re}from"react/jsx-runtime";function er(e){switch(e){case"combat":return"\u2694";case"elite":return"\u2605";case"rest":return"\u25B3";case"shop":return"$";case"event":return"?";case"boss":return"\u25C6";default:return"\xB7"}}function tr(e,t,a,r){if(r)return"cyan";if(a)return"yellow";if(e.visited||!t)return"gray";switch(e.type){case"combat":return"white";case"elite":return"red";case"rest":return"green";case"shop":return"yellow";case"event":return"magenta";case"boss":return"red";default:return"white"}}function Vt({map:e,currentNodeId:t,reachableIds:a,selectedNodeId:r}){let o=new Set(a),n=Math.max(...e.nodes.map(l=>l.row)),i=[];for(let l=0;l<=n;l++)i.push(e.nodes.filter(s=>s.row===l).sort((s,u)=>s.column-u.column));return Re(Ze,{flexDirection:"column",padding:1,children:[Re(L,{bold:!0,color:"yellow",children:["\u25C6 Act ",e.act," Map"]}),z(L,{children:" "}),i.map((l,s)=>Re(Ze,{children:[Re(L,{dimColor:!0,children:[String(s).padStart(2," ")," "]}),z(Ze,{gap:2,children:[0,1,2,3].map(u=>{let f=l.find(F=>F.column===u);if(!f)return z(L,{children:" "},u);let p=f.id===t,y=o.has(f.id),k=f.id===r,H=tr(f,y,k,p),P=er(f.type),R=k?`[${P}]`:p?`(${P})`:` ${P} `;return z(L,{color:H,bold:k||p,dimColor:f.visited&&!p,children:R},u)})})]},s)),z(L,{children:" "}),z(L,{dimColor:!0,children:"\u2694=fight \u2605=elite \u25B3=rest $=shop ?=event \u25C6=boss"}),z(L,{dimColor:!0,children:"\u2191\u2193 select Enter proceed d=deck q=quit"})]})}import{Box as ar,Text as ye}from"ink";import{jsx as et,jsxs as Qe}from"react/jsx-runtime";function Ot({items:e,selectedIndex:t,fragments:a}){return Qe(ar,{flexDirection:"column",padding:1,children:[Qe(ye,{bold:!0,color:"yellow",children:["\u25C6 Shop \u2014 ",a," fragments"]}),et(ye,{children:" "}),e.map((r,o)=>{let n=o===t,i=a>=r.cost,l="";return r.type==="card"&&r.cardDef?l=`${r.cardDef.name} (${r.cardDef.rarity}) \u2014 ${r.cardDef.description}`:r.type==="potion"&&r.potionDef?l=`${r.potionDef.name} \u2014 ${r.potionDef.description}`:r.type==="card_remove"&&(l="Remove a card from your deck"),Qe(ye,{bold:n,color:n?"yellow":i?"white":"gray",children:[n?"\u25B6 ":" ",l," [",r.cost,"f]"]},o)}),et(ye,{children:" "}),et(ye,{dimColor:!0,children:"\u2191\u2193 navigate Enter buy q leave"})]})}import{Box as rr,Text as Y}from"ink";import{jsx as ve,jsxs as tt}from"react/jsx-runtime";function Ut({event:e,selectedChoice:t}){return tt(rr,{flexDirection:"column",padding:1,children:[tt(Y,{bold:!0,color:"magenta",children:["\u25C6 ",e.name]}),ve(Y,{children:" "}),ve(Y,{children:e.description}),ve(Y,{children:" "}),e.choices.map((a,r)=>tt(Y,{bold:r===t,color:r===t?"yellow":"white",children:[r===t?"\u25B6 ":" ",r+1,". ",a.label]},r)),ve(Y,{children:" "}),ve(Y,{dimColor:!0,children:"\u2191\u2193 navigate Enter choose"})]})}import{Box as nr,Text as X}from"ink";import{jsx as se,jsxs as Kt}from"react/jsx-runtime";function Lt({gateHp:e,gateMaxHp:t,selectedChoice:a,deckSize:r}){let o=Math.floor(t*.3),n=[`Rest \u2014 Heal ${o} Gate HP (${e} \u2192 ${Math.min(t,e+o)}/${t})`,`Smith \u2014 Upgrade a random card (deck: ${r})`];return Kt(nr,{flexDirection:"column",padding:1,children:[se(X,{bold:!0,color:"green",children:"\u25C6 Rest Site"}),se(X,{children:" "}),se(X,{children:"The campfire crackles against the encroaching Pale."}),se(X,{children:" "}),n.map((i,l)=>Kt(X,{bold:l===a,color:l===a?"yellow":"white",children:[l===a?"\u25B6 ":" ",i]},l)),se(X,{children:" "}),se(X,{dimColor:!0,children:"\u2191\u2193 navigate Enter choose"})]})}import{useState as we,useCallback as ie,useRef as or}from"react";function jt(){let[e,t]=we(null),[a,r]=we(-1),[o,n]=we(2),[i,l]=we(""),[s,u]=we(!1),f=or(null),p=ie((g,T,b,Z,O)=>{let le=g??Ve(),be=T??Math.floor(Math.random()*2147483647),Q=mt(le,be,b,Z,O);f.current=Q,t({...Q}),r(-1),n(2),u(!1),l("Your turn. Select a card (1-5) and a column (\u2190\u2192).")},[]),y=(()=>{if(a<0||!e)return!1;let g=e.hand[a];if(!g)return!1;let T=G(g.defId);return T?T.effects.some(b=>b.type==="damage"&&(b.target==="single"||b.target==="column")):!1})(),k=ie(g=>{if(!f.current||f.current.phase!=="player"||g<0||g>=f.current.hand.length)return;let T=f.current.hand[g],b=G(T.defId);if(!b)return;if(b.cost>f.current.resolve){l(`Not enough Resolve for ${b.name} (costs ${b.cost}).`);return}r(g);let Z=b.effects.some(O=>O.type==="damage"&&(O.target==="single"||O.target==="column"));l(Z?`${b.name} selected. Choose column (\u2190\u2192), Enter to play.`:`${b.name} selected. Enter to play.`)},[]),H=ie(g=>{g>=0&&g<5&&n(g)},[]),P=ie(()=>{let g=f.current;if(!g||g.phase!=="player"||a<0)return;let T=g.hand[a];if(!T)return;let b=G(T.defId);b&&(ut(g,a,o,s),f.current=g,t({...g}),r(-1),u(!1),g.outcome==="win"?l("Victory! The Pale recedes."):g.outcome==="lose"?l("The Gate has fallen..."):l(`${s?"Emplaced":"Played"} ${b.name}. ${g.resolve} Resolve left.`))},[a,o,s]),R=ie(()=>{if(a<0||!f.current)return;let g=f.current.hand[a];if(!g)return;let T=G(g.defId);if(!T||T.type!=="emplace"){l("This card cannot be emplaced.");return}u(b=>!b),l(s?`${T.name}: cast mode`:`${T.name}: EMPLACE mode \u2014 place in a column`)},[a,s]),F=ie(()=>{let g=f.current;!g||g.phase!=="player"||(pt(g),f.current=g,t({...g}),r(-1),g.outcome==="win"?l("Victory! The Pale recedes."):g.outcome==="lose"?l("The Gate has fallen..."):l(`Turn ${g.turn}. Your move.`))},[]);return{combat:e,selectedCard:a,targetColumn:o,message:i,emplaceMode:s,selectCard:k,selectTarget:H,confirmPlay:P,endTurn:F,toggleEmplace:R,startCombat:p,needsTarget:y}}import{Fragment as Yt,jsx as v,jsxs as I}from"react/jsx-runtime";var qt=60,zt=18;function dr(){let{stdout:e}=lr(),[t,a]=B({columns:e?.columns??process.stdout.columns??80,rows:e?.rows??process.stdout.rows??24});return sr(()=>{let r=e??process.stdout,o=()=>a({columns:r.columns,rows:r.rows});return r.on("resize",o),()=>{r.off("resize",o)}},[e]),t}function mr({dryRun:e}){let{exit:t}=ir(),{columns:a,rows:r}=dr(),[o,n]=B("menu"),[i,l]=B(0),[s,u]=B(null),[f,p]=B(0),[y,k]=B([]),[H,P]=B(0),[R,F]=B([]),[g,T]=B(0),[b,Z]=B(null),[O,le]=B(0),[be,Q]=B(0),Qt=jt(),{combat:$,selectedCard:at,targetColumn:De,message:ea,emplaceMode:hr,selectCard:rt,selectTarget:nt,confirmPlay:ta,endTurn:aa,toggleEmplace:ra,startCombat:Ae,needsTarget:na}=Qt,oa=a<qt||r<zt,_=J(c=>{if(e)return;let d=re();d||(d=Pt("Warden")),d.activeRun=c,Ce(d)},[e]),sa=J(()=>{let c=`run-${Date.now()}`,d=yt(c);u(d),p(0),_(d),n("map")},[_]),ia=J(()=>{let c=re();c?.activeRun&&(u(c.activeRun),p(0),n("map"))},[]),ot=J(()=>s?gt(s.map,s.currentNodeId):[],[s]),ca=J(c=>{if(!s)return;let d=wt(s,c.id);if(c.type==="combat"||c.type==="elite"){let m=V(s.seed+c.id),h=N(m),W=ft(s.act,h,c.type==="elite");Ae(d.deck,m,d.gateHp,d.gateMaxHp,W.enemies),u(d),n("combat")}else if(c.type==="boss"){let m=V(s.seed+"-boss-"+s.act),h=It(s.act);Ae(d.deck,m,d.gateHp,d.gateMaxHp,h),u(d),n("combat")}else if(c.type==="shop"){let m=N(V(s.seed+c.id));F(xt(m)),T(0),u(d),n("shop")}else if(c.type==="event"){let m=N(V(s.seed+c.id));Z(_t(s.act,m)),le(0),u(d),n("event")}else c.type==="rest"&&(Q(0),u(d),n("rest"));_(d)},[s,Ae,_]),la=J(()=>{if(!$||!s)return;let c={...s,gateHp:$.gateHp};if($.outcome==="lose"){c={...c,combat:null},u(c),_(null),n("result");return}let d=s.currentNodeId?ue(s.map,s.currentNodeId):null,m=d?.type==="elite"?xe.elite:d?.type==="boss"?xe.boss:xe.combat;if(c=qe(c,m),d?.type==="boss"){s.act<3?(c=Tt(c),u(c),_(c),p(0),n("map")):(u(c),_(null),n("result"));return}let h=N(V(s.seed+(s.currentNodeId??"")+"-reward"));k(Ke(h)),P(0),u(c),_(c),n("reward")},[$,s,_]),st=J(c=>{if(!s)return;let d=s;c&&(d=Le(d,Ie(c.id))),u(d),_(d),p(0),n("map")},[s,_]),Me=[{label:"New Run",action:"new"},...re()?.activeRun?[{label:"Resume Run",action:"resume"}]:[],{label:"Quit",action:"quit"}];if(cr((c,d)=>{if(o==="menu"){if(d.upArrow||c==="k")l(m=>Math.max(0,m-1));else if(d.downArrow||c==="j")l(m=>Math.min(Me.length-1,m+1));else if(d.return){let m=Me[i]?.action;m==="new"?sa():m==="resume"?ia():t()}else c==="q"&&t();return}if(o==="deck"){(c==="q"||d.escape)&&n("map");return}if(o==="map"&&s){let m=ot();d.upArrow||c==="k"?p(h=>Math.max(0,h-1)):d.downArrow||c==="j"?p(h=>Math.min(m.length-1,h+1)):d.return&&m[f]?ca(m[f]):c==="d"?n("deck"):c==="q"&&(_(s),n("menu"));return}if(o==="reward"){let m=y.length;d.upArrow||c==="k"?P(h=>Math.max(0,h-1)):d.downArrow||c==="j"?P(h=>Math.min(m,h+1)):c==="s"?st(null):d.return&&st(H<y.length?y[H]:null);return}if(o==="shop"&&s){if(d.upArrow||c==="k")T(m=>Math.max(0,m-1));else if(d.downArrow||c==="j")T(m=>Math.min(R.length-1,m+1));else if(c==="q"||d.escape)p(0),n("map");else if(d.return){let m=R[g];if(m&&s.fragments>=m.cost){let h=bt(s,m.cost);if(!h)return;if(m.type==="card"&&m.cardDef)h=Le(h,Ie(m.cardDef.id));else if(m.type==="potion"&&m.potionDef){let W=kt(h,m.potionDef.id);W&&(h=W)}u(h),_(h),F(W=>W.filter((gr,da)=>da!==g)),T(0)}}return}if(o==="event"&&b&&s){if(d.upArrow||c==="k")le(m=>Math.max(0,m-1));else if(d.downArrow||c==="j")le(m=>Math.min(b.choices.length-1,m+1));else if(d.return){let m=b.choices[O],h=s;switch(m.effect.type){case"heal":h=je(h,m.effect.value);break;case"damage":h={...h,gateHp:Math.max(1,h.gateHp-m.effect.value)};break;case"fragments":h=qe(h,m.effect.value);break;case"max_hp":h={...h,gateMaxHp:h.gateMaxHp+m.effect.value};break;case"card_reward":{let W=N(V(s.seed+(b?.id??"")));k(Ke(W)),P(0),u(h),_(h),n("reward");return}case"remove_card":{if(h.deck.length>5){let W=Math.floor(Math.random()*h.deck.length);h=vt(h,h.deck[W].instanceId)}break}}u(h),_(h),p(0),n("map")}return}if(o==="rest"&&s){if(d.upArrow||c==="k")Q(m=>Math.max(0,m-1));else if(d.downArrow||c==="j")Q(m=>Math.min(1,m+1));else if(d.return){let m=s;be===0&&(m=je(m,Math.floor(m.gateMaxHp*.3))),u(m),_(m),p(0),n("map")}return}if(o==="result"){(d.return||c===" "||c==="q")&&n("menu");return}if(o==="combat"&&$){if($.outcome!=="undecided"){(d.return||c===" ")&&la();return}if(c==="q"){n("menu");return}if(c==="d"&&s){n("deck");return}if(c==="e"){ra();return}if(c>="1"&&c<="9"){rt(parseInt(c)-1);return}if(d.leftArrow||c==="h"){nt(Math.max(0,De-1));return}if(d.rightArrow||c==="l"){nt(Math.min(4,De+1));return}if(d.return&&at>=0){ta();return}if(c===" "){aa();return}if(d.escape){rt(-1);return}}}),oa)return I(ce,{flexDirection:"column",padding:1,children:[v(w,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),I(w,{color:"red",children:["Terminal too small (",a,"x",r,", need ",qt,"x",zt,")"]})]});if(o==="menu")return I(ce,{flexDirection:"column",padding:1,children:[v(w,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),v(w,{children:" "}),v(w,{children:"The fortress stands at the edge of the Pale."}),v(w,{children:"Beyond the walls, something stirs."}),v(w,{children:" "}),Me.map((c,d)=>I(w,{bold:d===i,color:d===i?"yellow":void 0,children:[d===i?"\u25B6 ":" ",c.label]},c.action)),v(w,{children:" "}),v(w,{dimColor:!0,children:"\u2191\u2193 navigate Enter select q quit"})]});if(o==="deck"&&s)return v($t,{deck:s.deck});if(o==="map"&&s){let c=ot();return I(ce,{flexDirection:"column",children:[I(ce,{justifyContent:"space-between",paddingX:1,children:[I(w,{children:["Gate ",I(w,{bold:!0,color:s.gateHp>40?"green":s.gateHp>20?"yellow":"red",children:[s.gateHp,"/",s.gateMaxHp]})]}),I(w,{children:["Fragments ",v(w,{bold:!0,color:"yellow",children:s.fragments})]}),I(w,{children:["Deck ",v(w,{dimColor:!0,children:s.deck.length})]})]}),v(Vt,{map:s.map,currentNodeId:s.currentNodeId,reachableIds:c.map(d=>d.id),selectedNodeId:c[f]?.id??null})]})}if(o==="reward")return v(Ft,{cards:y,selectedIndex:H});if(o==="shop"&&s)return v(Ot,{items:R,selectedIndex:g,fragments:s.fragments});if(o==="event"&&b)return v(Ut,{event:b,selectedChoice:O});if(o==="rest"&&s)return v(Lt,{gateHp:s.gateHp,gateMaxHp:s.gateMaxHp,selectedChoice:be,deckSize:s.deck.length});if(o==="result"){let c=$?.outcome==="win"||s&&s.act>=3;return I(ce,{flexDirection:"column",padding:1,children:[v(w,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),v(w,{children:" "}),c?I(Yt,{children:[v(w,{bold:!0,color:"green",children:"\u2605 RUN COMPLETE"}),v(w,{children:"The Pale recedes. The Keep endures."})]}):I(Yt,{children:[v(w,{bold:!0,color:"red",children:"\u2717 DEFEAT"}),v(w,{children:"The Gate has fallen. The Pale consumes all."})]}),s&&I(w,{dimColor:!0,children:["Act ",s.act," | Deck: ",s.deck.length," cards | Fragments: ",s.fragments]}),v(w,{children:" "}),v(w,{dimColor:!0,children:"Press Enter to return to menu."})]})}return o==="combat"&&$&&s?I(ce,{flexDirection:"column",paddingX:1,children:[I(w,{dimColor:!0,children:["Act ",s.act," | Gate ",s.gateHp,"/",s.gateMaxHp," | Fragments ",s.fragments," | d=deck"]}),v(Nt,{combat:$,selectedCard:at,targetColumn:De,needsTarget:na,message:ea})]}):v(w,{children:"Loading..."})}function Xt(e){return v(Se,{children:v(mr,{...e})})}import{jsx as fr}from"react/jsx-runtime";var Jt="0.5.0";globalThis.__CODEKEEP_VERSION=Jt;var Zt=new ur;Zt.name("codekeep").description("CodeKeep: The Pale \u2014 deck-building tactical roguelike in your terminal").version(Jt).option("--ascii","Force ASCII-only rendering (no Unicode box drawing)").option("--compact","Compact layout for narrow terminals").option("--tutorial","Replay the tutorial").option("--no-save","Dry-run mode: play without writing to disk").action(e=>{(!process.stdin.isTTY||!process.stdout.isTTY)&&(process.stderr.write(`codekeep requires an interactive terminal.
19
+ `);return`https://github.com/tooyipjee/codekeep/issues/new?title=${t}&body=${r}&labels=bug,crash`}import{Fragment as la,jsx as V,jsxs as oe}from"react/jsx-runtime";var Ke=class extends Ir.Component{constructor(t){super(t),this.state={error:null,crashFilePath:null,issueUrl:null}}static getDerivedStateFromError(t){return{error:t}}componentDidCatch(t){try{let r=ia(t),a={timestamp:new Date().toISOString(),error:t.message,stack:t.stack,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch},o=ca(a);this.setState({crashFilePath:r,issueUrl:o})}catch{}}render(){return this.state.error?oe(Cr,{flexDirection:"column",padding:1,children:[V(S,{bold:!0,color:"red",children:"\u25C6 CodeKeep \u2014 Something went wrong"}),V(S,{children:" "}),V(S,{color:"red",children:this.state.error.message}),V(S,{children:" "}),V(S,{dimColor:!0,children:"Your save file is at:"}),oe(S,{children:[" ","~/.config/codekeep/game.json"]}),V(S,{children:" "}),this.state.crashFilePath&&oe(la,{children:[V(S,{dimColor:!0,children:"Crash report saved to:"}),oe(S,{children:[" ",this.state.crashFilePath]}),V(S,{children:" "})]}),this.state.issueUrl&&oe(la,{children:[V(S,{dimColor:!0,children:"Report this bug:"}),oe(S,{color:"cyan",children:[" ",this.state.issueUrl]}),V(S,{children:" "})]}),V(S,{dimColor:!0,children:"If the game won't start, try:"}),oe(S,{bold:!0,children:[" ","codekeep --tutorial"]}),V(S,{children:" "}),V(S,{dimColor:!0,children:"Press Ctrl+C to exit"})]}):this.props.children}};import{Box as gt,Text as O}from"ink";import{Box as Sr,Text as J}from"ink";import{jsx as Pe,jsxs as Ue}from"react/jsx-runtime";function Er(e){if(!e)return"";switch(e.type){case"advance":return`\u2193${e.value}`;case"attack":return`\u2694${e.value}`;case"buff":return`\u25B2${e.value}`;case"debuff":return`\u25BC${e.value}`;case"shield":return`\u25C8${e.value}`;case"summon":return`+${e.value}`;default:return""}}function Pr(e){let t=[];for(let r of e.statusEffects)switch(r.type){case"vulnerable":t.push(`V${r.stacks}`);break;case"weak":t.push(`W${r.stacks}`);break;case"burn":t.push(`B${r.stacks}`);break;case"empowered":t.push(`E${r.stacks}`);break;case"fortified":t.push(`F${r.stacks}`);break}return t.join("")}function Rr(e,t,r=6){let a=Math.max(0,Math.round(e/t*r));return"\u2588".repeat(a)+"\u2591".repeat(r-a)}function Dr(e){return`${ne(e.templateId)?.symbol??"?"}${e.hp}`}function da({columns:e,targetColumn:t,showTarget:r,gateHp:a,gateMaxHp:o,gateBlock:n}){let u=e.map((f,k)=>{let _=`Col ${k+1}`,N=f.emplacement?`[${f.emplacement.hp}hp]`:"",B=N?`${_}${N}`:_,M=r&&k===t,G=B.padStart(Math.floor((14+B.length)/2)).padEnd(14);return M?`[${G.slice(1,-1)}]`:` ${G.slice(1,-1)} `}).join("\u2502"),s=[];for(let f=0;f<Te;f++){let k=e.map(_=>{let N=_.enemies.filter(M=>M.row===f);return N.length===0?"\xB7".padStart(Math.floor(7.5)).padEnd(14):N.map(M=>{let G=Er(M.intent),U=Pr(M),h=Dr(M);return G&&(h+=` ${G}`),U&&(h+=` ${U}`),h}).join(" ").slice(0,14).padEnd(14)}).join("\u2502");s.push(k)}let p=Math.round(a/o*100),g=p>60?"green":p>30?"yellow":"red";return Ue(Sr,{flexDirection:"column",children:[Pe(J,{bold:!0,dimColor:!0,children:"\u2500".repeat(74)}),Pe(J,{bold:!0,children:u}),Pe(J,{dimColor:!0,children:"\u2500".repeat(74)}),s.map((f,k)=>Pe(J,{children:f},k)),Pe(J,{dimColor:!0,children:"\u2550".repeat(74)}),Ue(J,{children:[Ue(J,{bold:!0,color:g,children:["Gate ",Rr(a,o,12)," ",a,"/",o]}),n>0&&Ue(J,{color:"cyan",children:[" \u{1F6E1}",n]})]})]})}import{Box as Ar,Text as ve}from"ink";import{jsx as ma,jsxs as Re}from"react/jsx-runtime";function Mr(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function Hr(e){switch(e){case"armament":return"\u2694";case"fortification":return"\u{1F6E1}";case"edict":return"\u2726";case"wild":return"\u25C8";default:return"\xB7"}}function ua({hand:e,selectedIndex:t,resolve:r}){return e.length===0?ma(ve,{dimColor:!0,children:"No cards in hand."}):Re(Ar,{flexDirection:"column",children:[ma(ve,{bold:!0,children:"Hand:"}),e.map((a,o)=>{let n=$(a.defId);if(!n)return null;let l=o===t,u=n.cost<=r,s=l?"\u25B6 ":" ",p=`${o+1}`,g=`[${n.cost}]`;return Re(ve,{children:[Re(ve,{bold:l,color:l?"yellow":void 0,children:[s,p,"."," "]}),Re(ve,{color:u?Mr(n.rarity):"gray",bold:l,children:[Hr(n.category)," ",n.name," ",g]}),l&&Re(ve,{dimColor:!0,children:[" \u2014 ",n.description]})]},a.instanceId)})]})}import{jsx as K,jsxs as De}from"react/jsx-runtime";function pa({combat:e,selectedCard:t,targetColumn:r,needsTarget:a,message:o}){let n=e.columns.reduce((l,u)=>l+u.enemies.length,0);return De(gt,{flexDirection:"column",children:[De(gt,{justifyContent:"space-between",children:[K(O,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),De(O,{children:["Turn ",K(O,{bold:!0,children:e.turn})," ","Resolve ",De(O,{bold:!0,color:"cyan",children:[e.resolve,"/",e.maxResolve]})," ","Enemies ",K(O,{bold:!0,color:"red",children:n})," ","Draw ",K(O,{dimColor:!0,children:e.drawPile.length})," ","Discard ",K(O,{dimColor:!0,children:e.discardPile.length})]})]}),K(O,{children:" "}),K(da,{columns:e.columns,targetColumn:r,showTarget:a&&t>=0,gateHp:e.gateHp,gateMaxHp:e.gateMaxHp,gateBlock:e.gateBlock}),K(O,{children:" "}),K(ua,{hand:e.hand,selectedIndex:t,resolve:e.resolve}),K(O,{children:" "}),o&&K(O,{color:"yellow",children:o}),e.phase==="player"&&De(O,{dimColor:!0,children:["1-",e.hand.length," card \u2190\u2192 column Enter play e emplace p potion Space end turn d deck q quit"]}),e.outcome!=="undecided"&&K(gt,{marginTop:1,children:K(O,{bold:!0,color:e.outcome==="win"?"green":"red",children:e.outcome==="win"?"\u2605 VICTORY \u2014 The siege is broken!":"\u2717 DEFEAT \u2014 The Gate has fallen."})})]})}import{Box as fa,Text as Z}from"ink";import{jsx as we,jsxs as Le}from"react/jsx-runtime";function Br(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function ha({cards:e,selectedIndex:t}){return Le(fa,{flexDirection:"column",padding:1,children:[we(Z,{bold:!0,color:"yellow",children:"\u25C6 Choose a Card Reward"}),we(Z,{children:" "}),e.map((r,a)=>{let o=a===t;return Le(fa,{flexDirection:"column",marginBottom:1,children:[Le(Z,{bold:o,color:o?"yellow":Br(r.rarity),children:[o?"\u25B6 ":" ",a+1,". ",r.name," [",r.cost,"] (",r.rarity,")"]}),Le(Z,{dimColor:!0,children:[" ",r.description]})]},r.id)}),we(Z,{children:" "}),we(Z,{dimColor:!0,children:t>=0?` ${e.length+1}. Skip \u2014 take no card`:`\u25B6 ${e.length+1}. Skip \u2014 take no card`}),we(Z,{children:" "}),we(Z,{dimColor:!0,children:"\u2191\u2193 navigate Enter select s skip"})]})}import{Box as Gr,Text as Ae}from"ink";import{jsx as vt,jsxs as yt}from"react/jsx-runtime";function Nr(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function ga({deck:e}){let t=new Map;for(let a of e)t.set(a.defId,(t.get(a.defId)??0)+1);let r=[...t.entries()].map(([a,o])=>({def:$(a),count:o})).filter(a=>a.def).sort((a,o)=>{let n={common:0,uncommon:1,rare:2,legendary:3},l=n[a.def.rarity]??0,u=n[o.def.rarity]??0;return l!==u?l-u:a.def.name.localeCompare(o.def.name)});return yt(Gr,{flexDirection:"column",padding:1,children:[yt(Ae,{bold:!0,color:"yellow",children:["\u25C6 Your Deck"," (",e.length," cards)"]}),vt(Ae,{children:" "}),r.map(({def:a,count:o})=>yt(Ae,{color:Nr(a.rarity),children:[o>1?`${o}x `:" ",a.name," [",a.cost,"] \u2014 ",a.description]},a.id)),vt(Ae,{children:" "}),vt(Ae,{dimColor:!0,children:"Press q or Esc to go back."})]})}import{Box as wt,Text as Q}from"ink";import{jsx as se,jsxs as qe}from"react/jsx-runtime";function Wr(e){switch(e){case"combat":return"\u2694";case"elite":return"\u2605";case"rest":return"\u25B3";case"shop":return"$";case"event":return"?";case"boss":return"\u25C6";default:return"\xB7"}}function $r(e,t,r,a){if(a)return"cyan";if(r)return"yellow";if(e.visited||!t)return"gray";switch(e.type){case"combat":return"white";case"elite":return"red";case"rest":return"green";case"shop":return"yellow";case"event":return"magenta";case"boss":return"red";default:return"white"}}function ya({map:e,currentNodeId:t,reachableIds:r,selectedNodeId:a}){let o=new Set(r),n=Math.max(...e.nodes.map(u=>u.row)),l=[];for(let u=0;u<=n;u++)l.push(e.nodes.filter(s=>s.row===u).sort((s,p)=>s.column-p.column));return qe(wt,{flexDirection:"column",padding:1,children:[qe(Q,{bold:!0,color:"yellow",children:["\u25C6 Act ",e.act," Map"]}),se(Q,{children:" "}),l.map((u,s)=>qe(wt,{children:[qe(Q,{dimColor:!0,children:[String(s).padStart(2," ")," "]}),se(wt,{gap:2,children:[0,1,2,3].map(p=>{let g=u.find(G=>G.column===p);if(!g)return se(Q,{children:" "},p);let f=g.id===t,k=o.has(g.id),_=g.id===a,N=$r(g,k,_,f),B=Wr(g.type),M=_?`[${B}]`:f?`(${B})`:` ${B} `;return se(Q,{color:N,bold:_||f,dimColor:g.visited&&!f,children:M},p)})})]},s)),se(Q,{children:" "}),se(Q,{dimColor:!0,children:"\u2694=fight \u2605=elite \u25B3=rest $=shop ?=event \u25C6=boss"}),se(Q,{dimColor:!0,children:"\u2191\u2193 select Enter proceed d=deck q=quit"})]})}import{Box as Fr,Text as Me}from"ink";import{jsx as kt,jsxs as bt}from"react/jsx-runtime";function va({items:e,selectedIndex:t,fragments:r}){return bt(Fr,{flexDirection:"column",padding:1,children:[bt(Me,{bold:!0,color:"yellow",children:["\u25C6 Shop \u2014 ",r," fragments"]}),kt(Me,{children:" "}),e.map((a,o)=>{let n=o===t,l=r>=a.cost,u="";return a.type==="card"&&a.cardDef?u=`${a.cardDef.name} (${a.cardDef.rarity}) \u2014 ${a.cardDef.description}`:a.type==="potion"&&a.potionDef?u=`${a.potionDef.name} \u2014 ${a.potionDef.description}`:a.type==="card_remove"&&(u="Remove a card from your deck"),bt(Me,{bold:n,color:n?"yellow":l?"white":"gray",children:[n?"\u25B6 ":" ",u," [",a.cost,"f]"]},o)}),kt(Me,{children:" "}),kt(Me,{dimColor:!0,children:"\u2191\u2193 navigate Enter buy q leave"})]})}import{Box as Vr,Text as ie}from"ink";import{jsx as He,jsxs as Tt}from"react/jsx-runtime";function wa({event:e,selectedChoice:t}){return Tt(Vr,{flexDirection:"column",padding:1,children:[Tt(ie,{bold:!0,color:"magenta",children:["\u25C6 ",e.name]}),He(ie,{children:" "}),He(ie,{children:e.description}),He(ie,{children:" "}),e.choices.map((r,a)=>Tt(ie,{bold:a===t,color:a===t?"yellow":"white",children:[a===t?"\u25B6 ":" ",a+1,". ",r.label]},a)),He(ie,{children:" "}),He(ie,{dimColor:!0,children:"\u2191\u2193 navigate Enter choose"})]})}import{Box as Or,Text as ce}from"ink";import{jsx as be,jsxs as ba}from"react/jsx-runtime";function ka({gateHp:e,gateMaxHp:t,selectedChoice:r,deckSize:a}){let o=Math.floor(t*.3),n=[`Rest \u2014 Heal ${o} Gate HP (${e} \u2192 ${Math.min(t,e+o)}/${t})`,`Thin \u2014 Remove a card from your deck (${a} cards)`,"Leave \u2014 Continue without resting"];return ba(Or,{flexDirection:"column",padding:1,children:[be(ce,{bold:!0,color:"green",children:"\u25C6 Rest Site"}),be(ce,{children:" "}),be(ce,{children:"The campfire crackles against the encroaching Pale."}),be(ce,{children:" "}),n.map((l,u)=>ba(ce,{bold:u===r,color:u===r?"yellow":"white",children:[u===r?"\u25B6 ":" ",l]},u)),be(ce,{children:" "}),be(ce,{dimColor:!0,children:"\u2191\u2193 navigate Enter choose"})]})}import{Box as Kr,Text as le}from"ink";import{jsx as Be,jsxs as xt}from"react/jsx-runtime";var Ur={wren:"Wren, the Steward",sable:"Sable, the Archivist",duskmar:"Duskmar, First Wall",mott:"Mott, the Salvager",pale_visitor:"The Pale Visitor"};function Ta({keep:e,selectedIndex:t}){let r=[...q.map(a=>{let o=Ee(e,a.id);return{type:"structure",id:a.id,label:`${a.name} (Lv.${o}/${a.maxLevel}) \u2014 ${a.description}`}}),...e.npcs.map(a=>({type:"npc",id:a.id,label:`${Ur[a.id]??a.id} (Tier ${a.tier}/5)`})),{type:"action",id:"new_run",label:"Begin New Run"},{type:"action",id:"ascension",label:`Ascension Level: ${e.highestAscension}`}];return xt(Kr,{flexDirection:"column",padding:1,children:[Be(le,{bold:!0,color:"yellow",children:"\u25C6 The Keep"}),xt(le,{children:["Echoes: ",Be(le,{bold:!0,color:"cyan",children:e.echoes})," | Runs: ",e.totalRuns," | Wins: ",e.totalWins]}),Be(le,{children:" "}),r.map((a,o)=>xt(le,{bold:o===t,color:o===t?"yellow":"white",children:[o===t?"\u25B6 ":" ",a.label]},a.id)),Be(le,{children:" "}),Be(le,{dimColor:!0,children:"\u2191\u2193 navigate Enter select q quit"})]})}import{useState as Ge,useCallback as de,useRef as Lr}from"react";function xa(){let[e,t]=Ge(null),[r,a]=Ge(-1),[o,n]=Ge(2),[l,u]=Ge(""),[s,p]=Ge(!1),g=Lr(null),f=de((h,y,b,W,H)=>{let te=h??st(),ae=y??Math.floor(Math.random()*2147483647),C=$t(te,ae,b,W,H);g.current=C,t({...C}),a(-1),n(2),p(!1),u("Your turn. Select a card (1-5) and a column (\u2190\u2192).")},[]),k=(()=>{if(r<0||!e)return!1;let h=e.hand[r];if(!h)return!1;let y=$(h.defId);return y?y.effects.some(b=>b.type==="damage"&&(b.target==="single"||b.target==="column")):!1})(),_=de(h=>{if(!g.current||g.current.phase!=="player"||h<0||h>=g.current.hand.length)return;let y=g.current.hand[h],b=$(y.defId);if(!b)return;if(b.cost>g.current.resolve){u(`Not enough Resolve for ${b.name} (costs ${b.cost}).`);return}a(h);let W=b.effects.some(H=>H.type==="damage"&&(H.target==="single"||H.target==="column"));u(W?`${b.name} selected. Choose column (\u2190\u2192), Enter to play.`:`${b.name} selected. Enter to play.`)},[]),N=de(h=>{h>=0&&h<5&&n(h)},[]),B=de(()=>{let h=g.current;if(!h||h.phase!=="player"||r<0)return;let y=h.hand[r];if(!y)return;let b=$(y.defId);b&&(Ft(h,r,o,s),g.current=h,t({...h}),a(-1),p(!1),h.outcome==="win"?u("Victory! The Pale recedes."):h.outcome==="lose"?u("The Gate has fallen..."):u(`${s?"Emplaced":"Played"} ${b.name}. ${h.resolve} Resolve left.`))},[r,o,s]),M=de(()=>{if(r<0||!g.current)return;let h=g.current.hand[r];if(!h)return;let y=$(h.defId);if(!y||y.type!=="emplace"){u("This card cannot be emplaced.");return}p(b=>!b),u(s?`${y.name}: cast mode`:`${y.name}: EMPLACE mode \u2014 place in a column`)},[r,s]),G=de(()=>{let h=g.current;!h||h.phase!=="player"||(Vt(h),g.current=h,t({...h}),a(-1),h.outcome==="win"?u("Victory! The Pale recedes."):h.outcome==="lose"?u("The Gate has fallen..."):u(`Turn ${h.turn}. Your move.`))},[]),U=de(h=>{let y=g.current;if(!(!y||!h)){for(let b of h.effects)switch(b.type){case"heal":y.gateHp=Math.min(y.gateMaxHp,y.gateHp+b.value);break;case"block":y.gateBlock+=b.value;break;case"damage":{if(b.target==="column"){let W=y.columns[2];for(let H of W.enemies)H.hp-=b.value}else if(b.target==="all")for(let W of y.columns)for(let H of W.enemies)H.hp-=b.value;break}case"draw":{let W=F(y.seed+y.turn*53),{drawn:H,newDrawPile:te,newDiscardPile:ae}=Se(y.drawPile,y.discardPile,b.value,W);y.hand.push(...H),y.drawPile=te,y.discardPile=ae;break}case"resolve":y.resolve=Math.min(y.maxResolve+5,y.resolve+b.value);break}g.current=y,t({...y}),u(`Used ${h.name}.`)}},[]);return{combat:e,selectedCard:r,targetColumn:o,message:l,emplaceMode:s,selectCard:_,selectTarget:N,confirmPlay:B,endTurn:G,toggleEmplace:M,startCombat:f,applyPotion:U,needsTarget:k}}import{Fragment as _t,jsx as w,jsxs as T}from"react/jsx-runtime";var _a=60,Ia=18;function Xr(){let{stdout:e}=Yr(),[t,r]=E({columns:e?.columns??process.stdout.columns??80,rows:e?.rows??process.stdout.rows??24});return qr(()=>{let a=e??process.stdout,o=()=>r({columns:a.columns,rows:a.rows});return a.on("resize",o),()=>{a.off("resize",o)}},[e]),t}function Jr({dryRun:e}){let{exit:t}=jr(),{columns:r,rows:a}=Xr(),[o,n]=E("menu"),[l,u]=E(0),[s,p]=E(null),[g,f]=E(0),[k,_]=E([]),[N,B]=E(0),[M,G]=E([]),[U,h]=E(0),[y,b]=E(null),[W,H]=E(0),[te,ae]=E(0),[C,me]=E(null),[ue,je]=E(0),[It,pe]=E(""),[Ct,Pa]=E(!1),[ze,fe]=E(0),Ye=xa(),{combat:X,selectedCard:St,targetColumn:Xe,message:Ra,emplaceMode:tn,selectCard:Et,selectTarget:Pt,confirmPlay:Da,endTurn:Aa,toggleEmplace:Ma,startCombat:Je,applyPotion:an,needsTarget:Ha}=Ye,Ba=r<_a||a<Ia,L=j(()=>{let i=ye();return i||(i=oa("Warden"),i.keep.npcs.length===0&&(i.keep.npcs=pt()),Y(i)),i.keep.npcs.length===0&&(i.keep.npcs=pt()),i},[]),I=j(i=>{if(e)return;let d=L();d.activeRun=i,C&&(d.keep=C),Y(d)},[e,C,L]),Rt=j(()=>{let i=`run-${Date.now()}`,d=L(),c=Lt(i,d.keep.highestAscension);p(c),f(0),d.activeRun=c,d.keep.totalRuns++,me(d.keep),Y(d),n("map")},[L]),Ga=j(()=>{let i=ye();i?.activeRun&&(p(i.activeRun),me(i.keep),f(0),n("map"))},[]),Dt=j(()=>{let i=L();me(i.keep),je(0),pe(""),n("keep")},[L]),At=j(()=>s?Ut(s.map,s.currentNodeId):[],[s]),Na=j(i=>{if(!s)return;let d=qt(s,i.id);if(i.type==="combat"||i.type==="elite"){let c=z(s.seed+i.id),m=F(c),x=Ot(s.act,m,i.type==="elite");Je(d.deck,c,d.gateHp,d.gateMaxHp,x.enemies),p(d),n("combat")}else if(i.type==="boss"){let c=z(s.seed+"-boss-"+s.act),m=Wt(s.act);Je(d.deck,c,d.gateHp,d.gateMaxHp,m),p(d),n("combat")}else if(i.type==="shop"){let c=F(z(s.seed+i.id));G(Jt(c)),h(0),p(d),n("shop")}else if(i.type==="event"){let c=F(z(s.seed+i.id));b(Zt(s.act,c)),H(0),p(d),n("event")}else i.type==="rest"&&(ae(0),p(d),n("rest"));I(d)},[s,Je,I]),Ze=j((i,d)=>{Pa(d),p(i);let c=L(),m=ft(d,i.act,i.ascensionLevel);c.keep.echoes+=m,d&&(c.keep.totalWins++,i.ascensionLevel>=c.keep.highestAscension&&(c.keep.highestAscension=i.ascensionLevel+1)),c.activeRun=null,me(c.keep),Y(c),n("result")},[L]),Wa=j(()=>{if(!X||!s)return;let i={...s,gateHp:X.gateHp};if(X.outcome==="lose"){Ze(i,!1);return}let d=s.currentNodeId?Ce(s.map,s.currentNodeId):null,c=d?.type==="elite"?$e.elite:d?.type==="boss"?$e.boss:$e.combat;if(i=Oe(i,c),d?.type==="boss"){s.act<3?(i=Xt(i),p(i),I(i),f(0),n("map")):Ze(i,!0);return}let m=F(z(s.seed+(s.currentNodeId??"")+"-reward"));_(lt(m)),B(0),p(i),I(i),n("reward")},[X,s,I,Ze]),Mt=j(i=>{if(!s)return;let d=s;i&&(d=dt(d,Ve(i.id))),p(d),I(d),f(0),n("map")},[s,I]),Qe=[{label:"The Keep",action:"keep"},{label:"New Run",action:"new"},...ye()?.activeRun?[{label:"Resume Run",action:"resume"}]:[],{label:"Quit",action:"quit"}];if(zr((i,d)=>{if(o==="menu"){if(d.upArrow||i==="k")u(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")u(c=>Math.min(Qe.length-1,c+1));else if(d.return){let c=Qe[l]?.action;c==="keep"?Dt():c==="new"?Rt():c==="resume"?Ga():t()}else i==="q"&&t();return}if(o==="deck"){(i==="q"||d.escape)&&n("map");return}if(o==="map"&&s){let c=At();d.upArrow||i==="k"?f(m=>Math.max(0,m-1)):d.downArrow||i==="j"?f(m=>Math.min(c.length-1,m+1)):d.return&&c[g]?Na(c[g]):i==="d"?n("deck"):i==="q"&&(I(s),n("menu"));return}if(o==="reward"){let c=k.length;d.upArrow||i==="k"?B(m=>Math.max(0,m-1)):d.downArrow||i==="j"?B(m=>Math.min(c,m+1)):i==="s"?Mt(null):d.return&&Mt(N<k.length?k[N]:null);return}if(o==="shop"&&s){if(d.upArrow||i==="k")h(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")h(c=>Math.min(M.length-1,c+1));else if(i==="q"||d.escape)f(0),n("map");else if(d.return){let c=M[U];if(c&&s.fragments>=c.cost){let m=jt(s,c.cost);if(!m)return;if(c.type==="card"&&c.cardDef)m=dt(m,Ve(c.cardDef.id)),p(m),I(m),G(x=>x.filter((P,re)=>re!==U)),h(0);else if(c.type==="potion"&&c.potionDef){let x=zt(m,c.potionDef.id);x&&(m=x),p(m),I(m),G(P=>P.filter((re,Ne)=>Ne!==U)),h(0)}else c.type==="card_remove"&&(p(m),I(m),fe(0),G(x=>x.filter((P,re)=>re!==U)),n("shop_remove"))}}return}if(o==="event"&&y&&s){if(d.upArrow||i==="k")H(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")H(c=>Math.min(y.choices.length-1,c+1));else if(d.return){let c=y.choices[W],m=s;switch(c.effect.type){case"heal":m=ut(m,c.effect.value);break;case"damage":{m={...m,gateHp:Math.max(1,m.gateHp-c.effect.value)};let x=c.label.match(/(\d+)\s*fragments/i);x&&(m=Oe(m,parseInt(x[1])));break}case"fragments":m=Oe(m,c.effect.value);break;case"max_hp":m={...m,gateMaxHp:m.gateMaxHp+c.effect.value,gateHp:m.gateHp+c.effect.value};break;case"card_reward":{let x=c.label.match(/lose\s+(\d+)\s*hp/i);x&&(m={...m,gateHp:Math.max(1,m.gateHp-parseInt(x[1]))});let P=F(z(s.seed+(y?.id??"")));_(lt(P)),B(0),p(m),I(m),n("reward");return}case"remove_card":{p(m),I(m),n("deck_remove");return}case"nothing":break}p(m),I(m),f(0),n("map")}return}if(o==="rest"&&s){if(d.upArrow||i==="k")ae(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")ae(c=>Math.min(2,c+1));else if(d.return){let c=s;te===0?(c=ut(c,Math.floor(c.gateMaxHp*.3)),p(c),I(c),f(0),n("map")):te===1&&c.deck.length>5?(p(c),fe(0),n("deck_remove")):(p(c),I(c),f(0),n("map"))}return}if(o==="deck_remove"&&s){if(d.upArrow||i==="k")fe(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")fe(c=>Math.min(s.deck.length-1,c+1));else if(d.escape||i==="q")f(0),n("map");else if(d.return&&s.deck.length>5){let c=s.deck[ze];if(c){let m=mt(s,c.instanceId);p(m),I(m)}f(0),n("map")}return}if(o==="shop_remove"&&s){if(d.upArrow||i==="k")fe(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")fe(c=>Math.min(s.deck.length-1,c+1));else if(d.escape||i==="q")n("shop");else if(d.return&&s.deck.length>5){let c=s.deck[ze];if(c){let m=mt(s,c.instanceId);p(m),I(m)}n("shop")}return}if(o==="keep"&&C){let c=q.length+C.npcs.length+2;if(d.upArrow||i==="k")je(m=>Math.max(0,m-1));else if(d.downArrow||i==="j")je(m=>Math.min(c-1,m+1));else if(i==="q")n("menu");else if(d.return)if(ue<q.length){let m=q[ue],x=Qt(C,m.id);if(x){me(x);let P=L();P.keep=x,Y(P),pe(`Upgraded ${m.name}!`)}else Ee(C,m.id)>=m.maxLevel?pe(`${m.name} is already max level.`):pe(`Not enough Echoes to upgrade ${m.name}.`)}else if(ue<q.length+C.npcs.length){let m=ue-q.length,x=C.npcs[m],P=ea(x.id,C);if(P){let re=ta(C,x.id,P.dialogueId);me(re);let Ne=L();Ne.keep=re,Y(Ne),pe(`${P.speaker}: "${P.text}"`)}else pe("(No new dialogue)")}else ue===q.length+C.npcs.length&&Rt();return}if(o==="result"){d.return||i===" "?Dt():i==="q"&&n("menu");return}if(o==="combat"&&X){if(X.outcome!=="undecided"){(d.return||i===" ")&&Wa();return}if(i==="q"){n("menu");return}if(i==="d"&&s){n("deck");return}if(i==="e"){Ma();return}if(i==="p"&&s){let c=s.potions.findIndex(m=>m!==null);if(c>=0){let m=Yt(s,c);if(m&&Ye.combat){let x=_e.find(P=>P.id===m.potionId);p(m.run),I(m.run),Ye.applyPotion(x??null)}}return}if(i>="1"&&i<="9"){Et(parseInt(i)-1);return}if(d.leftArrow||i==="h"){Pt(Math.max(0,Xe-1));return}if(d.rightArrow||i==="l"){Pt(Math.min(4,Xe+1));return}if(d.return&&St>=0){Da();return}if(i===" "){Aa();return}if(d.escape){Et(-1);return}}}),Ba)return T(ee,{flexDirection:"column",padding:1,children:[w(v,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),T(v,{color:"red",children:["Terminal too small (",r,"x",a,", need ",_a,"x",Ia,")"]})]});if(o==="menu")return T(ee,{flexDirection:"column",padding:1,children:[w(v,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),w(v,{children:" "}),w(v,{children:"The fortress stands at the edge of the Pale."}),w(v,{children:"Beyond the walls, something stirs."}),w(v,{children:" "}),Qe.map((i,d)=>T(v,{bold:d===l,color:d===l?"yellow":void 0,children:[d===l?"\u25B6 ":" ",i.label]},i.action)),w(v,{children:" "}),w(v,{dimColor:!0,children:"\u2191\u2193 navigate Enter select q quit"})]});if(o==="deck"&&s)return w(ga,{deck:s.deck});if(o==="keep"&&C)return T(ee,{flexDirection:"column",children:[w(Ta,{keep:C,selectedIndex:ue}),It&&T(v,{color:"cyan",bold:!0,children:[" ",It]})]});if((o==="deck_remove"||o==="shop_remove")&&s)return T(ee,{flexDirection:"column",padding:1,children:[w(v,{bold:!0,color:"red",children:"\u25C6 Remove a Card"}),T(v,{dimColor:!0,children:["Choose a card to remove from your deck (",s.deck.length," cards)."]}),w(v,{children:" "}),s.deck.map((i,d)=>{let c=$(i.defId),m=d===ze;return T(v,{bold:m,color:m?"yellow":"white",children:[m?"\u25B6 ":" ",c?.name??i.defId," (",c?.rarity,") \u2014 ",c?.description??""]},i.instanceId)}),w(v,{children:" "}),w(v,{dimColor:!0,children:"\u2191\u2193 navigate Enter remove Esc cancel"})]});if(o==="map"&&s){let i=At();return T(ee,{flexDirection:"column",children:[T(ee,{justifyContent:"space-between",paddingX:1,children:[T(v,{children:["Gate ",T(v,{bold:!0,color:s.gateHp>40?"green":s.gateHp>20?"yellow":"red",children:[s.gateHp,"/",s.gateMaxHp]})]}),T(v,{children:["Fragments ",w(v,{bold:!0,color:"yellow",children:s.fragments})]}),T(v,{children:["Deck ",w(v,{dimColor:!0,children:s.deck.length})]})]}),w(ya,{map:s.map,currentNodeId:s.currentNodeId,reachableIds:i.map(d=>d.id),selectedNodeId:i[g]?.id??null})]})}if(o==="reward")return w(ha,{cards:k,selectedIndex:N});if(o==="shop"&&s)return w(va,{items:M,selectedIndex:U,fragments:s.fragments});if(o==="event"&&y)return w(wa,{event:y,selectedChoice:W});if(o==="rest"&&s)return w(ka,{gateHp:s.gateHp,gateMaxHp:s.gateMaxHp,selectedChoice:te,deckSize:s.deck.length});if(o==="result"){let i=s?ft(Ct,s.act,s.ascensionLevel):0;return T(ee,{flexDirection:"column",padding:1,children:[w(v,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),w(v,{children:" "}),Ct?T(_t,{children:[w(v,{bold:!0,color:"green",children:"\u2605 RUN COMPLETE"}),w(v,{children:"The Pale recedes. The Keep endures."})]}):T(_t,{children:[w(v,{bold:!0,color:"red",children:"\u2717 DEFEAT"}),w(v,{children:"The Gate has fallen. The Pale consumes all."})]}),s&&T(_t,{children:[T(v,{dimColor:!0,children:["Act ",s.act," | Deck: ",s.deck.length," cards | Fragments: ",s.fragments]}),T(v,{children:["Echoes earned: ",T(v,{bold:!0,color:"cyan",children:["+",i]})]})]}),w(v,{children:" "}),w(v,{dimColor:!0,children:"Enter \u2192 The Keep | q \u2192 menu"})]})}return o==="combat"&&X&&s?T(ee,{flexDirection:"column",paddingX:1,children:[T(v,{dimColor:!0,children:["Act ",s.act," | Gate ",s.gateHp,"/",s.gateMaxHp," | Fragments ",s.fragments," | d=deck"]}),w(pa,{combat:X,selectedCard:St,targetColumn:Xe,needsTarget:Ha,message:Ra})]}):w(v,{children:"Loading..."})}function Ca(e){return w(Ke,{children:w(Jr,{...e})})}import{jsx as en}from"react/jsx-runtime";var Sa="0.5.0";globalThis.__CODEKEEP_VERSION=Sa;var Ea=new Zr;Ea.name("codekeep").description("CodeKeep: The Pale \u2014 deck-building tactical roguelike in your terminal").version(Sa).option("--ascii","Force ASCII-only rendering (no Unicode box drawing)").option("--compact","Compact layout for narrow terminals").option("--tutorial","Replay the tutorial").option("--no-save","Dry-run mode: play without writing to disk").action(e=>{(!process.stdin.isTTY||!process.stdout.isTTY)&&(process.stderr.write(`codekeep requires an interactive terminal.
20
20
  Run it directly in your terminal (not piped or in CI).
21
- `),process.exit(1));let{waitUntilExit:t,unmount:a}=pr(fr(Xt,{asciiMode:e.ascii??!1,compact:e.compact??!1,forceTutorial:e.tutorial??!1,dryRun:e.save===!1}),{exitOnCtrlC:!1}),r=!1;function o(){if(!r){if(r=!0,e.save!==!1){process.stderr.write(`
21
+ `),process.exit(1));let{waitUntilExit:t,unmount:r}=Qr(en(Ca,{asciiMode:e.ascii??!1,compact:e.compact??!1,forceTutorial:e.tutorial??!1,dryRun:e.save===!1}),{exitOnCtrlC:!1}),a=!1;function o(){if(!a){if(a=!0,e.save!==!1){process.stderr.write(`
22
22
  Saving...
23
- `);try{let n=re();n&&Ce(n)}catch{}}a()}}process.on("SIGINT",o),process.on("SIGTERM",o),process.on("SIGHUP",o),t().then(()=>{process.exit(0)}).catch(()=>{process.exit(1)})});Zt.parse();export{Jt as CLI_VERSION};
23
+ `);try{let n=ye();n&&Y(n)}catch{}}r()}}process.on("SIGINT",o),process.on("SIGTERM",o),process.on("SIGHUP",o),t().then(()=>{process.exit(0)}).catch(()=>{process.exit(1)})});Ea.parse();export{Sa as CLI_VERSION};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codekeep",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "CodeKeep: The Pale — deck-building tactical roguelike in your terminal",
6
6
  "license": "MIT",