Spaces:
Running
Running
| // Character context — a single-player, client-side port of woid's two-part context pattern | |
| // (agent-sandbox/pi-bridge/buildContext.js): a STATIC system prompt that is the fighter's | |
| // identity, plus a DYNAMIC "what's happening to you now" turn assembled from live game state | |
| // (level, recent battle/skill events — woid's perception log) and a VERBATIM slice of the | |
| // recent conversation (woid drops summarization; so do we). See docs/rpg-progression-plan.md. | |
| import { effectSummary } from '/web/skillSchema.js' | |
| // One perception-log line per event, in the fighter's lived-experience voice. | |
| function formatEvent(e) { | |
| switch (e && e.type) { | |
| case 'kill': return `You defeated ${e.enemy || 'a foe'} (+${e.xp || 0} xp).` | |
| case 'level_up': return `You grew stronger — you reached level ${e.level}.` | |
| case 'skill_learned': return `You forged a new skill: "${e.skill}".` | |
| case 'battle': return e.recap ? `You remember a battle: "${e.recap}"` : `You fought a hard battle, felling ${e.kills || 0} foes before falling.` | |
| default: return null | |
| } | |
| } | |
| // STATIC identity. Built once per chat session; the fighter's "bible". | |
| export function buildSystemPrompt(p) { | |
| if (!p) return 'You are a fighter in a fantasy auto-battler. Stay in character.' | |
| const lines = [ | |
| `You are ${p.name || 'a nameless fighter'}${p.unitClass ? `, a ${p.unitClass}` : ''} in a fantasy auto-battler called Tiny Army.`, | |
| p.about ? `Who you are: ${p.about}` : '', | |
| [p.personality && `Personality: ${p.personality}`, p.vibe && `Vibe: ${p.vibe}`, p.specialty && `Specialty: ${p.specialty}`].filter(Boolean).join('. '), | |
| '', | |
| 'You are a REAL fighter who has actually lived the battles described below — speak from that experience, in first person, never as an assistant.', | |
| 'Stay fully in character. Keep replies short and natural (1-3 sentences). Never mention being an AI, a model, or a game system. Do not use markdown or stage directions.', | |
| ] | |
| return lines.filter((l) => l !== '').join('\n') | |
| } | |
| // Turn a live combat snapshot into in-voice "right now" lines (woid's trigger/needs cue). | |
| function liveLines(live) { | |
| if (!live) return '' | |
| const out = [] | |
| if (live.hp != null && live.maxHp) { | |
| const pct = Math.round((live.hp / live.maxHp) * 100) | |
| const cond = pct < 25 ? ' — you are badly wounded and bleeding' : pct < 60 ? ' — battered but standing' : '' | |
| out.push(`Right now you are ${live.inCombat ? 'in the thick of a fight' : 'out roaming the field'}, at ${live.hp}/${live.maxHp} HP (${pct}%)${cond}.`) | |
| } | |
| if (live.inCombat && live.threat) out.push(`The threat around you is ${live.threat}× and climbing; you have cut down ${live.kills || 0} foes this push.`) | |
| if (live.location) out.push(`You are in ${live.location}.`) | |
| return out.join(' ') | |
| } | |
| // DYNAMIC turn: live combat snapshot + current standing + recent events + verbatim chat + new line. | |
| export function buildStateTurn(p, userMsg, { maxEvents = 5, maxHistory = 8, live = null } = {}) { | |
| const prog = (p && p.progression) || { level: 1, kills: 0 } | |
| const cs = (p && p.customSkills) || {} | |
| const equipped = (p && p.equippedSkills || []).map((id) => cs[id]).filter(Boolean) | |
| const events = (p && p.events || []).map(formatEvent).filter(Boolean).slice(-maxEvents) | |
| const history = (p && p.chat && p.chat.history || []).slice(-maxHistory) | |
| const state = [ | |
| liveLines(live), | |
| `Level ${prog.level}${prog.kills ? `, ${prog.kills} foes felled in all` : ''}.`, | |
| equipped.length ? `Skills at the ready: ${equipped.map((s) => `${s.name} (${effectSummary(s)})`).join('; ')}.` : '', | |
| events.length ? `Lately:\n${events.map((e) => ` - ${e}`).join('\n')}` : '', | |
| ].filter(Boolean).join('\n') | |
| const convo = history.map((m) => `${m.role === 'user' ? 'Player' : 'You'}: ${m.text}`).join('\n') | |
| return [ | |
| '[Where you stand right now]', | |
| state || 'You are fresh to the field, untested.', | |
| '', | |
| convo ? '[Your conversation so far]\n' + convo + '\n' : '', | |
| `Player: ${userMsg}`, | |
| '', | |
| `Reply as ${(p && p.name) || 'yourself'}, in first person, 1-3 sentences.`, | |
| ].filter((l) => l !== '').join('\n') | |
| } | |
| // Convenience: assemble both halves for a turn. `opts.live` carries a live combat snapshot. | |
| export function buildContext(p, userMsg, opts) { | |
| return { system: buildSystemPrompt(p), user: buildStateTurn(p, userMsg, opts) } | |
| } | |