fix(workforce): a crew should not read as an alphabetical run
Seeding the name pick with the role index (0..n) started every crew at the top of the pool and took the next free names, so the first mission after the switch to per-mission crews hired Aarav, Abebe, Adaora, Adrian, Agnieszka. Unique and correct, and transparently generated. Seed from the claw's own uuid instead. UUIDv7 puts its random bytes LAST — the leading bytes are a timestamp, which would cluster the same way — so the tail is what spreads five picks across the whole pool. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
98037f9b3e
commit
d3a398716b
@@ -111,6 +111,43 @@ mod tests {
|
||||
assert!(NAMES.len() >= 150, "pool too small for one crew per mission");
|
||||
}
|
||||
|
||||
/// A crew should not read as an alphabetical run.
|
||||
///
|
||||
/// With the role index as the seed, every crew started at the top of the
|
||||
/// pool and took the next free names — the first real mission hired Aarav,
|
||||
/// Abebe, Adaora, Adrian, Agnieszka. Unique and correct, and obviously
|
||||
/// generated. Callers now seed from the claw's uuid tail, so this checks
|
||||
/// that well-spread seeds actually land in different regions of the pool
|
||||
/// rather than clustering at one end.
|
||||
#[test]
|
||||
fn spread_seeds_do_not_produce_an_alphabetical_run() {
|
||||
let index_of = |n: &str| NAMES.iter().position(|c| *c == n).expect("name in pool");
|
||||
let seeds = [
|
||||
0x9e37_79b9_7f4a_7c15u64,
|
||||
0x1234_5678_9abc_def0,
|
||||
0xfeed_face_dead_beef,
|
||||
0x0f0f_0f0f_f0f0_f0f0,
|
||||
0xa5a5_5a5a_c3c3_3c3c,
|
||||
];
|
||||
let mut taken: Vec<String> = Vec::new();
|
||||
let mut positions = Vec::new();
|
||||
for s in seeds {
|
||||
let n = pick(&taken, s);
|
||||
positions.push(index_of(&n) as i64);
|
||||
taken.push(n);
|
||||
}
|
||||
// Adjacent picks landing within a couple of slots of each other is the
|
||||
// clustering signature; require the crew to span a real distance.
|
||||
let (min, max) = (
|
||||
*positions.iter().min().unwrap(),
|
||||
*positions.iter().max().unwrap(),
|
||||
);
|
||||
assert!(
|
||||
max - min > (NAMES.len() as i64) / 3,
|
||||
"crew clustered in one region of the pool: {positions:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The scenario the operator actually asked for: consecutive missions must
|
||||
/// not hand back the same names. Reuse is off, so mission two staffs from
|
||||
/// what mission one left.
|
||||
|
||||
Reference in New Issue
Block a user