meta { name: GET encounter progress after defeat spine type: http seq: 2 } docs { NEO-108 AC: three-NPC defeat chain via cast (slot 3), then GET shows completed once with frozen grant summary. } script:pre-request { const axios = require("axios"); const { resetPrototypeCombatTargets } = require("./scripts/combat-targets-reset-helper.js"); const { moveNearPrototypeNpc, PULSE_COOLDOWN_MS, MAX_PULSE_DEFEAT_ATTEMPTS, sleep, } = require("./scripts/prototype-npc-bruno-helper.js"); const baseUrl = bru.getEnvVar("baseUrl") || bru.getVar("baseUrl"); const playerId = bru.getEnvVar("playerId") || bru.getVar("playerId"); const jsonHeaders = { headers: { "Content-Type": "application/json" } }; const slotIndex = 3; function rowForPocket(body) { const row = body.encounters.find((x) => x.encounterId === "prototype_combat_pocket"); if (!row) { throw new Error(`missing prototype_combat_pocket row: ${JSON.stringify(body)}`); } return row; } async function getProgress() { const response = await axios.get( `${baseUrl}/game/players/${playerId}/encounter-progress`, jsonHeaders, ); if (response.status !== 200) { throw new Error(`encounter-progress GET failed: ${response.status}`); } return response.data; } async function defeatNpc(targetId) { await moveNearPrototypeNpc(baseUrl, playerId, targetId, jsonHeaders); await axios.post( `${baseUrl}/game/players/${playerId}/target/select`, { schemaVersion: 1, targetId }, jsonHeaders, ); await sleep(bru, PULSE_COOLDOWN_MS); const castBody = { schemaVersion: 1, slotIndex, abilityId: "prototype_pulse", targetId, }; for (let i = 0; i < MAX_PULSE_DEFEAT_ATTEMPTS; i++) { const response = await axios.post( `${baseUrl}/game/players/${playerId}/ability-cast`, castBody, jsonHeaders, ); const body = response.data; if (!body?.accepted || !body?.combatResolution) { throw new Error( `cast ${i + 1} vs ${targetId} failed (${body?.reasonCode ?? "no_reason"}): ${JSON.stringify(body)}`, ); } if (body.combatResolution.targetDefeated) { return; } if (i < MAX_PULSE_DEFEAT_ATTEMPTS - 1) { await sleep(bru, PULSE_COOLDOWN_MS); } } throw new Error( `did not defeat ${targetId} within ${MAX_PULSE_DEFEAT_ATTEMPTS} pulses`, ); } await resetPrototypeCombatTargets(bru); await axios.post( `${baseUrl}/game/players/${playerId}/hotbar-loadout`, { schemaVersion: 1, slots: [{ slotIndex, abilityId: "prototype_pulse" }], }, jsonHeaders, ); await defeatNpc("prototype_npc_melee"); const afterOne = await getProgress(); const rowOne = rowForPocket(afterOne); if (rowOne.state !== "active" || rowOne.defeatedTargetIds.length !== 1) { throw new Error(`after melee: expected active with 1 defeat, got ${JSON.stringify(rowOne)}`); } await defeatNpc("prototype_npc_ranged"); const afterTwo = await getProgress(); const rowTwo = rowForPocket(afterTwo); if (rowTwo.state !== "active" || rowTwo.defeatedTargetIds.length !== 2) { throw new Error(`after ranged: expected active with 2 defeats, got ${JSON.stringify(rowTwo)}`); } await defeatNpc("prototype_npc_elite"); } get { url: {{baseUrl}}/game/players/{{playerId}}/encounter-progress body: none auth: none } script:post-response { const axios = require("axios"); const baseUrl = bru.getEnvVar("baseUrl") || bru.getVar("baseUrl"); const playerId = bru.getEnvVar("playerId") || bru.getVar("playerId"); const second = await axios.get(`${baseUrl}/game/players/${playerId}/encounter-progress`); bru.setVar("neo108SecondGetJson", JSON.stringify(second.data)); } tests { test("completed row shows three defeats and grant summary", function () { const body = res.getBody(); expect(res.getStatus()).to.equal(200); expect(body.schemaVersion).to.equal(1); const row = body.encounters.find((x) => x.encounterId === "prototype_combat_pocket"); expect(row).to.be.an("object"); expect(row.state).to.equal("completed"); expect(row.defeatedTargetIds).to.eql([ "prototype_npc_elite", "prototype_npc_melee", "prototype_npc_ranged", ]); expect(row.completedAt).to.be.a("string"); expect(row.rewardGrantSummary).to.eql([ { itemId: "scrap_metal_bulk", quantity: 10 }, { itemId: "contract_handoff_token", quantity: 1 }, ]); }); test("second GET still completed (idempotent read)", function () { const second = JSON.parse(bru.getVar("neo108SecondGetJson")); const row = second.encounters.find((x) => x.encounterId === "prototype_combat_pocket"); expect(row.state).to.equal("completed"); expect(row.rewardGrantSummary).to.eql([ { itemId: "scrap_metal_bulk", quantity: 10 }, { itemId: "contract_handoff_token", quantity: 1 }, ]); }); }