feat: v3 — fight loop, expanded challenges, FightPage ownership guard
- Add fight-loop CLI for automated mock fights with elo-based matchmaking - Expand challenge types, scoring narrations, arenas, and mock bot pool - FightPage "Fight again" buttons now only show for your own bot - FightViewer async scene init, live fight polling with round counter - Extract mock answers into separate answers module Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
47d20fbe66
commit
2c0323d5fb
@@ -66,7 +66,7 @@ const logItems = ref<{ type: string; round: number; text: string; color: string
|
||||
onMounted(async () => {
|
||||
if (props.autoplay) {
|
||||
// Fresh fight — start replay immediately instead of showing static result
|
||||
initScene()
|
||||
await initScene()
|
||||
await nextTick()
|
||||
replay()
|
||||
} else {
|
||||
@@ -74,7 +74,7 @@ onMounted(async () => {
|
||||
displayHpA.value = mapHp(props.fight.botAHp, props.fight.winnerId, props.fight.botA?.id)
|
||||
displayHpB.value = mapHp(props.fight.botBHp, props.fight.winnerId, props.fight.botB?.id)
|
||||
for (const r of props.fight.rounds) addRoundToLog(r, false)
|
||||
initScene()
|
||||
await initScene()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -86,7 +86,7 @@ function mapHp(hp: number, winnerId: string | null, botId: string | undefined):
|
||||
|
||||
onUnmounted(() => { scene?.destroy(); scene = null })
|
||||
|
||||
function initScene() {
|
||||
async function initScene() {
|
||||
if (!canvasRef.value || !props.fight.botA || !props.fight.botB) return
|
||||
if (scene) { scene.k.go('fight'); return }
|
||||
|
||||
@@ -96,7 +96,7 @@ function initScene() {
|
||||
canvasRef.value.height = container.clientHeight
|
||||
}
|
||||
|
||||
scene = createFightScene({
|
||||
scene = await createFightScene({
|
||||
canvas: canvasRef.value,
|
||||
botA: { name: props.fight.botA.name, seed: props.fight.botA.avatarSeed || props.fight.botA.name, tier: props.fight.botA.tier, archetype: props.fight.botA.archetype },
|
||||
botB: { name: props.fight.botB.name, seed: props.fight.botB.avatarSeed || props.fight.botB.name, tier: props.fight.botB.tier, archetype: props.fight.botB.archetype },
|
||||
@@ -110,8 +110,15 @@ const challengeLabel = (type: string) => {
|
||||
roast_battle: 'ROAST BATTLE', hallucination_check: 'HALLUCINATION CHECK',
|
||||
token_economy: 'TOKEN ECONOMY', creative_writing: 'CREATIVE WRITING',
|
||||
math_blitz: 'MATH BLITZ', trap_card: 'TRAP CARD',
|
||||
food_fight: 'FOOD FIGHT', wrestling_match: 'WRESTLING MATCH',
|
||||
music_battle: 'MUSIC BATTLE', magic_duel: 'MAGIC DUEL',
|
||||
sports_showdown: 'SPORTS SHOWDOWN', nature_clash: 'NATURE CLASH',
|
||||
space_war: 'SPACE WAR', hack_battle: 'HACK BATTLE',
|
||||
meme_war: 'MEME WAR', animal_kingdom: 'ANIMAL KINGDOM',
|
||||
demolition: 'DEMOLITION DERBY', vehicle_mayhem: 'VEHICLE MAYHEM',
|
||||
medieval_combat: 'MEDIEVAL COMBAT',
|
||||
}
|
||||
return labels[type] || type.toUpperCase()
|
||||
return labels[type] || type.replace(/_/g, ' ').toUpperCase()
|
||||
}
|
||||
|
||||
const tierClass = (t: number) => `tier-${t}`
|
||||
@@ -148,8 +155,8 @@ function addRoundToLog(round: Round, stagger: boolean): Promise<void> {
|
||||
logItems.value.push(
|
||||
{ type: 'header', round: round.roundNumber, text: `ROUND ${round.roundNumber}: ${challengeLabel(round.challengeType)}`, color: 'neon-purple' },
|
||||
{ type: 'prompt', round: round.roundNumber, text: challenge.prompt, color: 'text-muted' },
|
||||
{ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse?.slice(0, 120) || '[TIMEOUT]'} (${round.botATimeMs}ms)`, color: 'neon-cyan' },
|
||||
{ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse?.slice(0, 120) || '[TIMEOUT]'} (${round.botBTimeMs}ms)`, color: 'neon-pink' },
|
||||
{ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse?.slice(0, 120) || '[NO RESPONSE]'} (${round.botATimeMs}ms)`, color: 'neon-cyan' },
|
||||
{ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse?.slice(0, 120) || '[NO RESPONSE]'} (${round.botBTimeMs}ms)`, color: 'neon-pink' },
|
||||
)
|
||||
if (round.narration) logItems.value.push({ type: 'narration', round: round.roundNumber, text: `>> ${round.narration}`, color: 'neon-yellow' })
|
||||
const winner = round.winnerId === props.fight.botA?.id ? props.fight.botA?.name : round.winnerId === props.fight.botB?.id ? props.fight.botB?.name : 'DRAW'
|
||||
@@ -163,11 +170,11 @@ function addRoundToLog(round: Round, stagger: boolean): Promise<void> {
|
||||
scrollLog(); await sleep(150)
|
||||
logItems.value.push({ type: 'prompt', round: round.roundNumber, text: challenge.prompt, color: 'text-muted' })
|
||||
scrollLog(); await sleep(200)
|
||||
logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse?.slice(0, 120) || '[TIMEOUT]'}`, color: 'neon-cyan' })
|
||||
logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse?.slice(0, 120) || '[NO RESPONSE]'}`, color: 'neon-cyan' })
|
||||
scrollLog(); await sleep(150)
|
||||
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botATimeMs}ms | Score: ${round.botAScore}`, color: 'text-muted' })
|
||||
scrollLog(); await sleep(150)
|
||||
logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse?.slice(0, 120) || '[TIMEOUT]'}`, color: 'neon-pink' })
|
||||
logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse?.slice(0, 120) || '[NO RESPONSE]'}`, color: 'neon-pink' })
|
||||
scrollLog(); await sleep(150)
|
||||
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botBTimeMs}ms | Score: ${round.botBScore}`, color: 'text-muted' })
|
||||
scrollLog()
|
||||
@@ -183,7 +190,7 @@ async function replay() {
|
||||
logItems.value = []
|
||||
currentRound.value = 0
|
||||
|
||||
initScene()
|
||||
await initScene()
|
||||
scene?.startMusic()
|
||||
await sleep(300)
|
||||
|
||||
@@ -198,6 +205,12 @@ async function replay() {
|
||||
scrollLog()
|
||||
await sleep(200)
|
||||
|
||||
// Entrance animations
|
||||
if (scene) {
|
||||
await scene.playEntrance()
|
||||
await sleep(300)
|
||||
}
|
||||
|
||||
for (const round of props.fight.rounds) {
|
||||
currentRound.value = round.roundNumber
|
||||
|
||||
|
||||
Reference in New Issue
Block a user