fix: apply review feedback — injectable RNG, Fisher-Yates, objection routing, pacing, doc fix

Co-authored-by: PatrickFanella <61631520+PatrickFanella@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-01 15:31:40 +00:00
parent 99fec3c669
commit d8a3d5f312
8 changed files with 29 additions and 16 deletions

View File

@@ -124,7 +124,7 @@ Categories and intent:
| `src/court/phases/session-flow.ts` | Rewrite `runWitnessExamPhase`; update `PAUSE_MS` constants |
| `src/court/phases/witness-script.ts` | New — `buildWitnessScripts()`, `WitnessScript` type |
| `src/court/phases/random-events.ts` | New — event catalogue, `checkRandomEvent()` |
| `src/court/phases/objections.ts` | New — `checkObjection()` (organic flag check + classifier call + judge ruling) |
| `src/court/phases/objections.ts` | New — `handleObjectionRound()` (organic flag check + classifier call + judge ruling) |
| `src/llm/client.ts` | Rewrite `mockReply()` with randomised versatile lines |
| Test files | New unit tests for script builder, random events, objection classifier |

2
package-lock.json generated
View File

@@ -29,7 +29,7 @@
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tsx": "^4.20.5",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vite": "^6.0.7"
}

View File

@@ -25,8 +25,8 @@
"express-rate-limit": "^8.2.1",
"howler": "^2.2.4",
"pixi.js": "^8.0.0",
"prom-client": "^15.1.3",
"postgres": "^3.4.5",
"prom-client": "^15.1.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tmi.js": "^1.8.5"
@@ -41,7 +41,7 @@
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tsx": "^4.20.5",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vite": "^6.0.7"
}

View File

@@ -48,6 +48,7 @@ export interface ObjectionRoundInput {
store: CourtSessionStore;
session: CourtSession;
pause: (ms: number) => Promise<void>;
pauseMs: number;
}
/**
@@ -76,7 +77,7 @@ export async function handleObjectionRound(input: ObjectionRoundInput): Promise<
role: input.objectingRole,
userInstruction: `Object to the preceding testimony on grounds of ${objectionType}. Begin your turn with "OBJECTION:" followed by the type and a one-sentence explanation.`,
});
await input.pause(600);
await input.pause(input.pauseMs);
}
// Judge always rules

View File

@@ -16,9 +16,11 @@ describe('checkRandomEvent', () => {
});
it('returns at most one event per call', () => {
// Even when rng fires everything, only one event is returned
// Even when rng fires everything, only one event is returned (never an array)
const result = checkRandomEvent(() => 0.0);
assert.ok(result === null || typeof result.id === 'string');
assert.ok(
result === null || (!Array.isArray(result) && typeof result.id === 'string'),
);
});
it('all RANDOM_EVENTS have required fields', () => {

View File

@@ -51,8 +51,12 @@ export const RANDOM_EVENTS: RandomEvent[] = [
* Returns at most one event; returns null if none fire.
*/
export function checkRandomEvent(rng: () => number = Math.random): RandomEvent | null {
// Shuffle catalogue so higher-probability events don't always win on ties
const shuffled = [...RANDOM_EVENTS].sort(() => rng() - 0.5);
// Fisher-Yates shuffle so higher-probability events don't always win on ties
const shuffled = [...RANDOM_EVENTS];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.min(i, Math.floor(rng() * (i + 1)));
[shuffled[i], shuffled[j]] = [shuffled[j]!, shuffled[i]!];
}
for (const event of shuffled) {
if (rng() < event.probability) {
return event;

View File

@@ -29,6 +29,7 @@ const PHASE_DURATION_MS = {
const PAUSE_MS = {
casePromptAfterCue: 2_000,
witnessBetweenCycles: 3_000,
witnessBetweenTurns: 2_500,
recapLeadIn: 1_500,
} as const;
@@ -358,7 +359,7 @@ export async function runWitnessExamPhase(
session: context.session,
speaker: prosecutor,
role: 'prosecutor',
userInstruction: `Ask ${witnessConfig.displayName} a focused question about the core accusation. Direct examination question ${q + 1} of ${script.directRounds}. If you have grounds to object to anything said previously, begin with "OBJECTION:" followed by the type.`,
userInstruction: `Ask ${witnessConfig.displayName} a focused question about the core accusation. Direct examination question ${q + 1} of ${script.directRounds}.`,
});
await context.pause(displayPauseMs(prosecutorTurn.dialogue));
@@ -406,7 +407,7 @@ export async function runWitnessExamPhase(
await context.pause(displayPauseMs(judgeInterruptTurn.dialogue));
} else {
await handleObjectionRound({
dialogue: witnessTurn.dialogue,
dialogue: prosecutorTurn.dialogue,
objectingAgentId: defense,
objectingRole: 'defense',
judgeAgentId: judge,
@@ -414,6 +415,7 @@ export async function runWitnessExamPhase(
store: context.store,
session: context.session,
pause: context.pause,
pauseMs: PAUSE_MS.witnessBetweenTurns,
});
}
}
@@ -425,7 +427,7 @@ export async function runWitnessExamPhase(
session: context.session,
speaker: defense,
role: 'defense',
userInstruction: `Cross-examine ${witnessConfig.displayName} with one pointed challenge. Cross question ${q + 1} of ${script.crossRounds}. If you have grounds to object to anything said previously, begin with "OBJECTION:" followed by the type.`,
userInstruction: `Cross-examine ${witnessConfig.displayName} with one pointed challenge. Cross question ${q + 1} of ${script.crossRounds}.`,
});
await context.pause(displayPauseMs(defenseTurn.dialogue));
@@ -473,7 +475,7 @@ export async function runWitnessExamPhase(
await context.pause(displayPauseMs(judgeInterruptTurn.dialogue));
} else {
await handleObjectionRound({
dialogue: witnessCrossTurn.dialogue,
dialogue: defenseTurn.dialogue,
objectingAgentId: prosecutor,
objectingRole: 'prosecutor',
judgeAgentId: judge,
@@ -481,6 +483,7 @@ export async function runWitnessExamPhase(
store: context.store,
session: context.session,
pause: context.pause,
pauseMs: PAUSE_MS.witnessBetweenTurns,
});
}
}

View File

@@ -3,9 +3,12 @@ export interface WitnessScript {
crossRounds: number; // 25
}
export function buildWitnessScripts(witnessCount: number): WitnessScript[] {
export function buildWitnessScripts(
witnessCount: number,
rng: () => number = Math.random,
): WitnessScript[] {
return Array.from({ length: witnessCount }, () => ({
directRounds: Math.floor(Math.random() * 5) + 3,
crossRounds: Math.floor(Math.random() * 4) + 2,
directRounds: Math.floor(rng() * 5) + 3,
crossRounds: Math.floor(rng() * 4) + 2,
}));
}