fix(gameplay): start campaigns in playable state
Some checks failed
CI / validate (pull_request) Has been cancelled
CI / validate (push) Successful in 32m54s

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-08-21 19:41:50 +05:00
parent 19693c78c4
commit f7ca158fe4
9 changed files with 590 additions and 44 deletions

View File

@@ -51,7 +51,11 @@ const myIntent = computed(() => payload.value?.intents.find(intent =>
intent.member_id === currentMember.value?.id || intent.character_id === myCharacter.value?.id,
))
const isReady = computed(() => Boolean(myIntent.value?.ready))
const activeHumanMembers = computed(() => payload.value?.members.filter(member => member.active !== false) ?? [])
const activeHumanMembers = computed(() => payload.value?.members.filter(member =>
member.active !== false && payload.value?.characters.some(character =>
character.user_id === member.user_id && character.controller === 'human',
),
) ?? [])
const readyCount = computed(() => activeHumanMembers.value.filter(member => payload.value?.intents.some(intent => intent.member_id === member.id && intent.ready)).length)
const canAct = computed(() => currentRound.value?.status === 'open' && Boolean(myCharacter.value))
const roundBusy = computed(() => ['queued', 'resolving'].includes(String(currentRound.value?.status)))

View File

@@ -16,6 +16,8 @@ interface StoredSession {
confirmed_world_id?: string | null
}
const questionLimit = 4
const { api } = useDngApi()
const route = useRoute()
const stage = ref<Stage>('seed')
@@ -33,7 +35,14 @@ const messages = ref<Message[]>([
])
const displayedQuestion = computed(() => currentQuestion.value ? { key: currentQuestion.value.id, label: currentQuestion.value.label, options: currentQuestion.value.options } : null)
const progress = computed(() => stage.value === 'preview' || stage.value === 'confirming' ? 100 : stage.value === 'generating' ? 85 : stage.value === 'seed' ? 10 : 20 + Math.round((Math.min(answerCount.value, 5) / 5) * 55))
const conversationMessages = computed(() => {
if (!currentQuestion.value) return messages.value
const currentIndex = messages.value.findLastIndex(message =>
message.role === 'coauthor' && message.body === currentQuestion.value?.label,
)
return currentIndex < 0 ? messages.value : messages.value.filter((_, index) => index !== currentIndex)
})
const progress = computed(() => stage.value === 'preview' || stage.value === 'confirming' ? 100 : stage.value === 'generating' ? 85 : stage.value === 'seed' ? 10 : 20 + Math.round((Math.min(answerCount.value, questionLimit) / questionLimit) * 55))
function addMessage(role: Message['role'], body: string, label?: string) {
messages.value.push({ id: `${Date.now()}-${messages.value.length}`, role, body, label })
@@ -117,6 +126,7 @@ async function resume(id: string) {
async function answerQuestion(key: string, value: string) {
if (stage.value !== 'questions' || currentQuestion.value?.id !== key || busy.value || !sessionId.value) return
const answeredQuestion = currentQuestion.value
busy.value = true
pendingAnswer.value = { key, value }
try {
@@ -124,9 +134,10 @@ async function answerQuestion(key: string, value: string) {
method: 'POST', body: { content: value },
})
answers[key] = value
addMessage('coauthor', answeredQuestion.label, `COAUTHOR · QUESTION ${Math.min(answerCount.value + 1, questionLimit)} OF ${questionLimit}`)
addMessage('player', value)
currentQuestion.value = null
answerCount.value = Math.min(answerCount.value + 1, 5)
answerCount.value = Math.min(answerCount.value + 1, questionLimit)
pendingAnswer.value = null
retryAction.value = 'respond'
await requestNextQuestion()
@@ -140,14 +151,13 @@ async function answerQuestion(key: string, value: string) {
async function requestNextQuestion() {
if (!sessionId.value) return
const result = await api<RespondResult>(`/api/v1/coauthor/sessions/${sessionId.value}/respond`, { method: 'POST' })
if (result.readyToGenerate || answerCount.value >= 5) {
if (result.readyToGenerate || answerCount.value >= questionLimit) {
currentQuestion.value = null
await generate()
return
}
if (!result.question) throw new Error('The coauthor did not return its next question.')
currentQuestion.value = result.question
addMessage('coauthor', result.question.label, `COAUTHOR · QUESTION ${Math.min(answerCount.value + 1, 5)} OF UP TO 5`)
}
function errorText(error: unknown) {
@@ -247,7 +257,7 @@ onMounted(() => {
<main v-if="stage !== 'preview' && stage !== 'confirming'" class="coauthor">
<p class="kicker">COAUTHOR / SESSION 01</p>
<h1>BUILD THE<br>IMPOSSIBLE<span>.</span></h1>
<CoauthorConversation :messages="messages" :question="displayedQuestion" :selected-answer="currentQuestion ? answers[currentQuestion.id] : undefined" :busy="stage === 'generating' || busy" @answer="answerQuestion" @retry="retry" />
<CoauthorConversation :messages="conversationMessages" :question="displayedQuestion" :selected-answer="currentQuestion ? answers[currentQuestion.id] : undefined" :busy="stage === 'generating' || busy" @answer="answerQuestion" @retry="retry" />
<form v-if="stage === 'seed'" class="seed" @submit.prevent="begin">
<textarea v-model="seed" aria-label="World idea" rows="3" maxlength="1200" autofocus />
<button :disabled="!seed.trim() || busy">{{ busy ? 'SAVING…' : 'BEGIN' }} <span></span></button>