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:
@@ -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
2
package-lock.json
generated
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,12 @@ export interface WitnessScript {
|
||||
crossRounds: number; // 2–5
|
||||
}
|
||||
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user