Deterministic selection patterns using djb2 hashing, content-derived context strings, and reproducibility guarantees. Covers the deterministicSelect utility, CI enforcement, and migration patterns from Math.random(). Use when adding random selection to server code, ensuring reproducibility, or fixing non-determinism.
Expert knowledge for ensuring reproducible, deterministic behavior across the media pipeline.
Same input → same output, every time. No
Math.random()in production code.
This is enforced by CI. The lint job in .github/workflows/ci.yml greps for Math.random() in server/lib/ and fails the build if found (except in allowed locations).
Location: server/lib/deterministicSelect.ts
import { deterministicSelect, deterministicIndex } from "./deterministicSelect";
// Select an item from an array — always returns the same item for same context
const sfx = deterministicSelect(SFX_LIBRARY, `diegetic_${timestamp}_${contentType}`);
// Get just the index
const idx = deterministicIndex(array.length, `context_string`);
Uses the djb2 hash algorithm — fast, simple, good distribution:
function djb2Hash(str: string): number {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
}
return hash >>> 0; // Ensure unsigned 32-bit
}
// Selection: hash(context) % array.length
// Select item from array (throws if array empty)
function deterministicSelect<T>(array: readonly T[], context: string): T;
// Select index from length (throws if length ≤ 0)
function deterministicIndex(length: number, context: string): number;
The context string is the key to good deterministic selection. It should:
// Includes: purpose + timestamp + content type
`diegetic_${timestamp}_${contentType}`
// Includes: purpose + timestamp + emotion
`reaction_${timestamp}_${emotion}`
// Includes: purpose + two timestamps (for transitions)
`riser_${curr.timestamp}_${prev.timestamp}`
// Includes: purpose + primary emotion
`hook_question_${primaryEmotion}`
// Too generic — same selection for all peaks
`peak`
// Includes runtime state — not reproducible
`sfx_${Date.now()}`
// Includes random value — defeats the purpose
`reaction_${Math.random()}`
// ❌ WRONG — different result each run
const sfx = library[Math.floor(Math.random() * library.length)];
// ✅ CORRECT — same result for same inputs
import { deterministicSelect } from "./deterministicSelect";
const sfx = deterministicSelect(library, `diegetic_${timestamp}_${contentType}`);
import { deterministicSelect } from "./deterministicSelect";array[Math.floor(Math.random() * array.length)] with deterministicSelect(array, context)| Module | Selections Fixed | Context Patterns |
|---|---|---|
soundDesign.ts | 4 | diegetic_${peak}_${type}, reaction_${ts}_${to}, riser_${ts1}_${ts2}, emphasis_${ts}_${intensity} |
endingCraft.ts | 3 | laugh_${ts}_${intensity}, shock_${ts}_${intensity}, twist_${duration} |
reactionAmplification.ts | 1 | freeze_${ts}_${emotion} |
patternInterrupt.ts | 1 | interrupt_${timestamp} |
showBible/acts.ts | 3 | hook_question_${emotion}, hook_statistic_${emotion}, hook_teaser_${emotion} |
The lint job in .github/workflows/ci.yml includes:
- name: Check for Math.random() in server/lib (determinism)
run: |
VIOLATIONS=$(grep -rn "Math\.random()" server/lib/ \
--include="*.ts" \
| grep -v "vodDownloader/types.ts" \
| grep -v "deterministicSelect.ts" \
| grep -v "// " \
| grep -v "* " \
|| true)
if [ -n "$VIOLATIONS" ]; then
echo "::error::Math.random() found in server/lib/. Use deterministicSelect() instead."
echo "$VIOLATIONS"
exit 1
fi
| Location | Why |
|---|---|
vodDownloader/types.ts | Retry jitter for network resilience (acceptable non-determinism) |
deterministicSelect.ts | Documentation/comments reference Math.random() |
Comments (//, *) | Documentation only |
Tests in server/__tests__/ MAY use Math.random() (e.g., randomId(), randomScore()). The CI check only covers server/lib/ production code.
describe("deterministicSelect", () => {
it("should always return same result for same context", () => {
const array = ["a", "b", "c", "d", "e"];
const result1 = deterministicSelect(array, "test_context");
const result2 = deterministicSelect(array, "test_context");
expect(result1).toBe(result2);
});
it("should return different results for different contexts", () => {
const array = ["a", "b", "c", "d", "e"];
const result1 = deterministicSelect(array, "context_1");
const result2 = deterministicSelect(array, "context_2");
// Different contexts should (usually) select different items
// This is probabilistic but with 5 items, collision is ~20%
});
it("should throw for empty array", () => {
expect(() => deterministicSelect([], "context")).toThrow();
});
});
When adding code that needs to select from an array:
Math.random() in server/lib/deterministicSelect from ./deterministicSelect