Fuller banter pools, and the blow's own names decide who dealt pain and who killed

Damage lands on the defender's step, so a trigger keyed to the actor
never credited the attacker: across thirty three-bot games the kill line
fired zero times and the dealt-pain line five, against 328 takes. Both
now read the attackResolved, punched, and died events themselves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-22 09:39:05 -04:00
co-authored by Claude Fable 5.1
parent be96dd6047
commit c4a555f8df
2 changed files with 244 additions and 22 deletions
+237 -17
View File
@@ -12,6 +12,11 @@ export type BanterTrigger =
| "grabGold" | "deliverGold" | "dealPain" | "takePain" | "kill" | "die"
| "summon" | "buildWall" | "escape" | "springTrap" | "dodge" | "win";
// Voice guide: Hunter speaks in clipped businesslike capitals; Berserker
// delights in a good fight, also in capitals; Worrier frets in lowercase,
// with flashes of practical courage. Lines must fit every event in their
// trigger: deliveries need not score, summons vary, and either win counts.
export const BOT_LINES: Record<AutomatonStyle, Partial<Record<BanterTrigger, string[]>>> = {
hunter: {
grabGold: [
@@ -19,101 +24,316 @@ export const BOT_LINES: Record<AutomatonStyle, Partial<Record<BanterTrigger, str
"YOUR GOLD HAS BEEN REALLOCATED.",
"ASSET SECURED.",
"APPRAISAL: ADEQUATE. TAKING IT ANYWAY.",
"CHAIN OF CUSTODY: ME.",
"AN EXCELLENT ADDITION TO MY PORTFOLIO.",
"POSSESSION SIMPLIFIES THE PAPERWORK.",
"THIS WILL TRAVEL AS HAND LUGGAGE.",
],
deliverGold: [
"DELIVERY CONFIRMED. PLEASURE DOING BUSINESS.",
"LOGISTICS IS MY LOVE LANGUAGE.",
"RECEIPT AVAILABLE UPON REQUEST.",
"THE COURIER WOULD APPRECIATE A QUIET MOMENT.",
"NO DELIVERY SURCHARGE. THIS TIME.",
"PLEASE ADMIRE THE EFFICIENT DISTRIBUTION NETWORK.",
"ANOTHER SATISFIED ME.",
"A GOOD PLACE TO PUT SOMETHING VALUABLE.",
],
dealPain: [
"AN INEFFICIENT USE OF YOUR TURN.",
"COSTS HAVE BEEN PASSED TO THE CONSUMER.",
"THIS IS BILLABLE.",
"A SMALL HANDLING CHARGE.",
"PLEASE DIRECT COMPLAINTS TO THE FIREBALL DEPARTMENT.",
"NEGOTIATIONS HAVE BECOME EXPENSIVE.",
"I PREFER GOLD. THIS WILL DO.",
"CONSIDER THAT AN ITEMIZED OBJECTION.",
],
takePain: [
"DAMAGE NOTED. INVOICE PENDING.",
"THAT COMES OUT OF YOUR ESTATE.",
"I DID NOT APPROVE THIS EXPENDITURE.",
"THAT WAS NOT IN THE QUOTATION.",
"MY ROBE IS NOT A DEPRECIATING ASSET.",
"I SHALL BE REVIEWING OUR ARRANGEMENT.",
"THE PROFIT MARGIN NARROWS.",
"CUSTOMER RELATIONS WILL HEAR OF THIS.",
],
kill: [
"INVENTORY TRANSFERRED. CONDOLENCES.",
"ACCOUNT CLOSED.",
"ONE FEWER BIDDER.",
"YOUR CUSTOM WAS BRIEF BUT MEMORABLE.",
"COMPETITION HAS BEEN REDUCED.",
"A MOMENT OF SILENCE. THEN THE AUDIT.",
],
die: [
"FILING FOR BANKRUPTCY.",
"THE MARKET HAS CORRECTED.",
"PLEASE FORWARD MY MAIL TO THE ESTATE.",
"I OBJECT TO THE CLOSING FIGURES.",
"THIS WAS SUPPOSED TO BE A GROWTH OPPORTUNITY.",
"MY EXECUTOR WILL BE IN TOUCH.",
],
summon: [
"A SUBCONTRACTOR HAS BEEN RETAINED.",
"WELCOME TO THE FIRM. MIND THE WALLS.",
"ADDITIONAL STAFF. SAME OVERHEAD.",
"THE CONTRACT SPECIFIES MAZE WORK.",
"A PROMISING APPLICANT. REFERENCES WAIVED.",
"PLEASE KEEP PERSONAL FEUDS ON COMPANY TIME.",
],
buildWall: [
"ZONING APPROVED.",
"PROPERTY DEVELOPMENT.",
"ACCESS IS NOW A NEGOTIABLE BENEFIT.",
"THIS CORRIDOR HAS BEEN RESTRUCTURED.",
"AN INVESTMENT IN SEPARATION.",
"ALL COMPLAINTS MUST GO THE LONG WAY ROUND.",
],
escape: [
"RELOCATION EXPENSES: JUSTIFIED.",
"I SHALL CONDUCT BUSINESS FROM ELSEWHERE.",
"NEW PREMISES. SAME AMBITIONS.",
"PLEASE UPDATE YOUR ADDRESS BOOK.",
"COMMUTING IS FOR PEOPLE WITHOUT SPELLS.",
"A CHANGE OF ADDRESS, NOT OF OWNERSHIP.",
],
springTrap: [
"AN UNFORESEEN LIABILITY.",
"I SHOULD HAVE READ THE SMALL PRINT.",
"THIS DECK HAS HIDDEN FEES.",
"AN UNWELCOME ENCLOSURE.",
"WHO SIGNED OFF ON THIS CARD?",
"THAT CLAUSE WAS NOT DISCLOSED.",
],
dodge: [
"PROJECTIONS SAID YOU WOULD MISS.",
"DELIVERY REFUSED.",
"YOUR ESTIMATE WAS OPTIMISTIC.",
"NO CHARGE RECEIVED.",
"A WELCOME ROUNDING ERROR.",
"YOU MAY WISH TO REVISE YOUR PROJECTION.",
],
win: [
"PLEASURE DOING BUSINESS WITH ALL OF YOU.",
"THE BOOKS BALANCE. IN MY FAVOR.",
"A SATISFACTORY RETURN ON WIZARDRY.",
"THANK YOU FOR YOUR CONTRIBUTIONS.",
"I LOOK FORWARD TO OUR NEXT TRANSACTION.",
"THE FORECAST WAS GOLDEN.",
],
summon: ["A SUBCONTRACTOR HAS BEEN RETAINED."],
buildWall: ["ZONING APPROVED."],
escape: ["RELOCATION EXPENSES: JUSTIFIED."],
springTrap: ["AN UNFORESEEN LIABILITY."],
dodge: ["PROJECTIONS SAID YOU WOULD MISS."],
win: ["PLEASURE DOING BUSINESS WITH ALL OF YOU."],
},
berserker: {
grabGold: [
"GOLD IS MERELY BAIT.",
"SHINY. IRRELEVANT.",
"COME AND ARGUE ABOUT IT.",
"HEAVY. I LIKE HEAVY.",
"DOES THIS COME WITH AN ANGRY OWNER?",
"I HOPE SOMEBODY WANTS THIS BACK.",
"A TROPHY WITH A HANDLE.",
"THIS SHOULD GET YOUR ATTENTION.",
],
deliverGold: [
"THERE. NOW MY HANDS ARE FREE.",
"ENOUGH CARRYING. WHERE WERE WE?",
"THE SHINY THING CAN WAIT HERE.",
"I HAVE BEEN VERY RESPONSIBLE. NOW FOR THE FUN.",
"IF YOU WANT IT, COME THROUGH ME.",
"GOLD DOWN. CHIN UP.",
"A FINE TROPHY. NEEDS A SCORCH MARK.",
"I CAN CARRY TREASURE AND A GRUDGE.",
],
dealPain: [
"YES. AGAIN.",
"PAIN RECEIPT PRINTED.",
"HOLD STILL. THIS IS THE FUN PART.",
"THE MAZE PROVIDES.",
"THAT IS THE SOUND I CAME FOR.",
"MUCH BETTER THAN SMALL TALK.",
"WE ARE GETTING SOMEWHERE NOW.",
"FINALLY. A PROPER CONVERSATION.",
],
takePain: [
"GOOD. NOW IT IS INTERESTING.",
"I FELT THAT. DO IT AGAIN.",
"THERE YOU ARE!",
"A WORTHY INTRODUCTION.",
"I WAS BEGINNING TO FEEL NEGLECTED.",
"THAT ONE HAD CONVICTION.",
"WE SHALL CALL THAT THE WARM-UP.",
"I LIKE YOUR ATTITUDE. KEEP IT WITHIN REACH.",
],
kill: [
"SCHEDULED DEMISE: DELIVERED.",
"NEXT.",
"THE PILE GROWS.",
"THE MAZE ECHOES BETTER NOW.",
"A GOOD FIGHT. WHO WANTS THE NEXT ONE?",
"YOU MADE ME WORK FOR THAT.",
],
die: [
"SAVE MY SEAT.",
"I WILL BE BACK FOR THE REMATCH.",
"NOW THAT WAS AN ENDING.",
"I DEMAND A LOUDER FUNERAL.",
"BURY ME FACING THE FIGHT.",
"A FINE BLOW. I RESENT IT ENORMOUSLY.",
],
summon: [
"I HAVE MADE YOU A FRIEND. IT IS NOT FRIENDLY.",
"IT EATS WHAT I TELL IT TO EAT.",
"AT LAST. SOMEONE WITH MY INTERESTS.",
"WELCOME. PICK A FEUD.",
"THE MAZE COULD USE MORE TROUBLE.",
"TRY TO LEAVE SOME FOR ME.",
],
buildWall: [
"MORE CORNERS TO TRAP YOU IN.",
"LET THE MAZE HAVE A SAY IN THIS.",
"THE LONG WAY ROUND BUILDS CHARACTER.",
"A WALL TODAY. RUBBLE EVENTUALLY.",
"NOW THIS CORRIDOR HAS SOME BACKBONE.",
"A LITTLE SCENERY FOR OUR NEXT ARGUMENT.",
],
escape: [
"A TACTICAL LEAP. NOT A RETREAT.",
"A BETTER ANGLE FOR THE NEXT BLOW.",
"I GO WHERE THE TROUBLE NEEDS ME.",
"SAME FEUD. DIFFERENT SQUARE.",
"DO NOT MISTAKE DISTANCE FOR FORGIVENESS.",
"WALKING WAS TAKING TOO LONG.",
],
springTrap: [
"WHO PUT THAT THERE. RESPECT.",
"THE DECK HAS TEETH. EXCELLENT.",
"EVEN THE CARDS WANT A FIGHT.",
"A RUDE LITTLE PIECE OF CARDBOARD.",
"THAT CARD HAS SPIRIT.",
"I WAS EXPECTING A SPELL. THIS HAS PERSONALITY.",
],
dodge: [
"SWING HARDER.",
"I WAS RIGHT THERE!",
"THE AIR WILL NEVER RECOVER.",
"PUT SOME FEELING INTO IT.",
"A TERRIBLE WASTE OF HOSTILITY.",
"ALL THAT FUSS. NO BRUISE.",
],
win: [
"LAST ONE STANDING. AS FORETOLD.",
"A FINE MAZE. LET US DO THAT AGAIN.",
"I WAS JUST GETTING COMFORTABLE.",
"VICTORY! WHO WANTS A REMATCH?",
"THE MAZE WILL REMEMBER THE NOISE.",
"A GOOD DAY TO BE DIFFICULT.",
],
buildWall: ["MORE CORNERS TO TRAP YOU IN."],
escape: ["A TACTICAL LEAP. NOT A RETREAT."],
springTrap: ["WHO PUT THAT THERE. RESPECT."],
dodge: ["SWING HARDER."],
win: ["LAST ONE STANDING. AS FORETOLD."],
},
worrier: {
grabGold: [
"taking this. sorry. sorry.",
"borrowing it. permanently. sorry.",
"please don't rattle. they'll hear us.",
"it looked lighter from over there.",
"mine for now. a very nervous now.",
"i can explain. preferably from farther away.",
"one treasure. several new concerns.",
"this is going to make me conspicuous.",
],
deliverGold: [
"home. safe. breathe.",
"there. i would like to stop carrying problems now.",
"stay. please be good at staying.",
"down you go. quietly.",
"that was much too much adventure.",
"i shall worry about it from here.",
"a place for everything. preferably behind a wall.",
"good. now i can wring both hands.",
],
deliverGold: ["home. safe. breathe."],
dealPain: [
"ow. logged.",
"unnecessary!",
"that looked like it hurt. it was supposed to. i think.",
"could we consider that a warning and stop?",
"i practiced that. nervously.",
"please don't make this a feud.",
"i'd feel worse if you were farther away.",
"that was my brave thing for today.",
],
takePain: [
"rude.",
"i wrote it down. the list is long.",
"yes, that was one of the things i feared.",
"i preferred the worrying to the confirmation.",
"i was using those life-points.",
"my concerns have become very specific.",
"could you put future complaints in writing?",
"i should have stayed behind the other wall.",
],
kill: [
"oh no. oh no. it worked?",
"i'm not proud. mostly.",
"is it over? that part, i mean.",
"one less worry. a new kind of guilt.",
"please don't haunt me. i'm busy enough.",
"i think i need to sit down. somewhere else.",
],
die: [
"called it.",
"this is fine. this is fine. this is not fine.",
"i had a whole list of things left to worry about.",
"at least nobody can ask me to go first now.",
"please put 'quite reasonably concerned' on the stone.",
"i knew i should have packed a second robe.",
],
summon: [
"please be nice to it. actually don't.",
"hello. we should discuss the exits.",
"please look braver than i do.",
"i made company. now i'm responsible for company.",
"we're both new to this arrangement.",
"i hope you brought your own courage.",
],
summon: ["please be nice to it. actually don't."],
buildWall: [
"good wall. safe wall.",
"one more wall between me and everything.",
"finally. something solid to agree with me.",
"i like a conversation with a wall in it.",
"yes. more of that between us.",
"a little privacy for my panic.",
],
escape: [
"nope nope nope.",
"here seems promising. let me worry about it.",
"a change of scenery for the same concerns.",
"i left before i could talk myself out of it.",
"please send any further unpleasantness to my old square.",
"i would call that graceful if i weren't shaking.",
],
springTrap: [
"i knew the floor was suspicious.",
"i had a bad feeling about the next card. every next card.",
"the deck and i are no longer on speaking terms.",
"i would like to put that back unread.",
"even the paper is against me.",
"i worried about the wrong thing. briefly.",
],
dodge: [
"missed me. oh thank goodness. i mean: ha.",
"i shall take full credit once i stop trembling.",
"excellent. nothing happened to me.",
"i had a complaint prepared and everything.",
"i'm going to need a moment to appreciate that.",
"please don't correct your mistake.",
],
win: [
"wait. me? check again.",
"we can stop now, yes? officially?",
"i'd like the quietest possible victory parade.",
"all that worrying. apparently useful.",
"i survived the maze. now there's the attention.",
"thank you. please celebrate from a sensible distance.",
],
escape: ["nope nope nope."],
springTrap: ["i knew the floor was suspicious."],
dodge: ["missed me. oh thank goodness. i mean: ha."],
win: ["wait. me? check again."],
},
};
+7 -5
View File
@@ -617,15 +617,17 @@ function banterTrigger(
if (actor === seat) {
if (e.type === "treasurePickedUp" && e.player === seat) return "grabGold";
if (e.type === "treasureDropped" && e.player === seat && e.onHomeOf != null) return "deliverGold";
if (e.type === "damaged" && e.player !== seat) return "dealPain";
if (e.type === "died" && e.killedBy === seat && e.player !== seat) return "kill";
if (e.type === "creatureCreated" && e.controller === seat) return "summon";
if (e.type === "wallCreated" && e.caster === seat) return "buildWall";
if (e.type === "trapSprung" && e.player === seat) return "springTrap";
}
// A counter-teleport escape resolves during the ATTACKER's step, and a
// last-standing win can land on the victim's turn: self-referential
// triggers hold whoever acted.
// Blows land on the DEFENDER's step (their pass or counter resolves the
// stack), a counter-teleport escape resolves during the attacker's, and
// a last-standing win can land on the victim's turn: these triggers
// read the event's own names, not whoever acted.
if (e.type === "attackResolved" && e.attacker === seat && e.defender !== seat && (e.damageDealt as number) > 0) return "dealPain";
if (e.type === "punched" && e.attacker === seat) return "dealPain";
if (e.type === "died" && e.killedBy === seat && e.player !== seat) return "kill";
if (e.type === "teleported" && e.player === seat && e.by === seat) return "escape";
if (e.type === "gameWon" && e.player === seat) return "win";
if (e.type === "damaged" && e.player === seat) return "takePain";