feat: NPC noir genre table pack + genre registry
Implements Slice 2 Task 1: Noir/Blade Runner genre pack with curated string tables (names, looks, manners, wants, secrets, voices) and a registry mapping id->pack for use by offline NPC generator. Tests: 2/2 pass (noir structure + registry resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import { noir } from './noir.js';
|
||||
|
||||
const packs = new Map([[noir.id, noir]]);
|
||||
|
||||
export function getGenrePack(id) { return packs.get(id) || null; }
|
||||
export function registerGenrePack(pack) { packs.set(pack.id, pack); }
|
||||
export function listGenrePacks() {
|
||||
return [...packs.values()].map((p) => ({ id: p.id, label: p.label }));
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
export const noir = {
|
||||
id: 'noir',
|
||||
label: 'Noir / Blade Runner',
|
||||
tables: {
|
||||
firstNames: ['Otto', 'Mara', 'Cyrus', 'Dita', 'Lev', 'Priya', 'Sol', 'Neena', 'Bram', 'Vesna'],
|
||||
surnames: ['Brandt', 'Okoye', 'Vance', 'Marlow', 'Sato', 'Kessler', 'Reyes', 'Dahl', 'Cole', 'Nyx'],
|
||||
looks: [
|
||||
'Heavyset, oil-stained cuffs, a rosary they keep touching',
|
||||
'Rail-thin, chrome tooth, coat two sizes too big',
|
||||
'Ex-military posture gone to seed, nicotine-yellow fingers',
|
||||
'Immaculate suit, one cracked lens in their glasses',
|
||||
'Burn scar down one wrist, always half in shadow',
|
||||
'Too-bright dye job, roots showing, tired eyes',
|
||||
'Synthetic-smooth skin that never quite reads as alive',
|
||||
'Missing a fingertip, hides the hand when they notice you looking',
|
||||
],
|
||||
manners: [
|
||||
'Over-friendly, laughs a beat too late',
|
||||
'Answers questions with questions',
|
||||
'Won\'t sit with their back to the door',
|
||||
'Counts their words like they cost money',
|
||||
'Flatters you, then watches to see if it landed',
|
||||
'Keeps checking a watch that isn\'t there',
|
||||
'Talks fast when lying, slow when it\'s worse',
|
||||
],
|
||||
wants: [
|
||||
'The debt gone before their shift ends',
|
||||
'A name - someone else to take the fall',
|
||||
'Out of the city on the last transport',
|
||||
'To be believed, just once',
|
||||
'The photograph back before anyone else sees it',
|
||||
'Protection they\'re too proud to ask for',
|
||||
],
|
||||
secrets: [
|
||||
'Signed off the container they never inspected',
|
||||
'Is already talking to the other side',
|
||||
'The person you\'re looking for is family',
|
||||
'Sold the access codes months ago',
|
||||
'Isn\'t who their papers say they are',
|
||||
'Watched it happen and did nothing',
|
||||
],
|
||||
voices: [
|
||||
'Says "you understand me?" after everything',
|
||||
'Never finishes the last word of a sentence',
|
||||
'Drops into a second language when rattled',
|
||||
'Calls everyone "friend" like a threat',
|
||||
'Over-enunciates, like reciting',
|
||||
'Laughs at their own lines before they land',
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { noir } from '../../src/npc/packs/noir.js';
|
||||
import { getGenrePack, listGenrePacks } from '../../src/npc/packs/index.js';
|
||||
|
||||
const FIELDS = ['firstNames', 'surnames', 'looks', 'manners', 'wants', 'secrets', 'voices'];
|
||||
|
||||
describe('noir genre pack', () => {
|
||||
it('has an id, label, and all non-empty tables', () => {
|
||||
expect(noir.id).toBe('noir');
|
||||
expect(noir.label).toMatch(/noir/i);
|
||||
for (const f of FIELDS) {
|
||||
expect(Array.isArray(noir.tables[f]), `${f} is array`).toBe(true);
|
||||
expect(noir.tables[f].length, `${f} non-empty`).toBeGreaterThanOrEqual(6);
|
||||
expect(noir.tables[f].every((x) => typeof x === 'string' && x.length > 0)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('is resolvable from the registry', () => {
|
||||
expect(getGenrePack('noir')).toBe(noir);
|
||||
expect(getGenrePack('missing')).toBe(null);
|
||||
expect(listGenrePacks()).toEqual([{ id: 'noir', label: noir.label }]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user