web: sim store, URL state scheme, debounced parallel runs (M3 slice 2)
useSimStore is the one reactive module singleton from the spec: panels over a shared base config, runs debounced 400 ms, one POST /api/scenario per panel in parallel, results swapped in atomically only when every request succeeds. Failures keep the last good results; engine 400s are routed to an inline validationError, everything else to the banner message. Stale in-flight responses lose to newer runs per panel. URL state follows spec section 7 (readable params, repeated panel=, focus index, full fallback to the preset on any malformed part) and is covered by round-trip and rejection tests. The study copy and initial panels live in the deepfake-school preset module; formatPct is the single percent-rounding rule. The vitest tsconfig now keeps lib at ES2022 like the app config, and prettier skips the tygo-generated src/types (drift guard stays green).
This commit is contained in:
parent
2feb6ea5a3
commit
2f21248c20
13 changed files with 1003 additions and 4 deletions
49
web/src/presets/deepfake-school.ts
Normal file
49
web/src/presets/deepfake-school.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { StrategyMostConnected, StrategyNone, StrategyRandom } from '@/types/engine'
|
||||
import type { StudyPreset } from './types'
|
||||
|
||||
// The one study this deployment tells. A different research question is a
|
||||
// new preset file plus a changed import in the app; no component changes.
|
||||
|
||||
export const deepfakeSchoolPreset: StudyPreset = {
|
||||
headline: 'How a non‑consensual deepfake spreads through a school',
|
||||
narrative:
|
||||
'One simulated year group, 120 students. Educate the same 30% of them, ' +
|
||||
'but change who: the fake reaches {0} of the school with no program, ' +
|
||||
'{1} educating at random, and {2} educating the best‑connected students.',
|
||||
disclaimerShort: 'Illustrative model, not validated',
|
||||
disclaimerLong:
|
||||
'spreadlab runs a seeded agent-based toy simulation: a synthetic ' +
|
||||
'friendship network of one school year group, a fixed chance to forward ' +
|
||||
'the fake along each friendship per round, and an education program that ' +
|
||||
'teaches some students to refuse. Its parameters are chosen for ' +
|
||||
'illustration, not fitted to data, so it is not a validated prediction ' +
|
||||
'of any real school. Use it to build intuition about who to educate, ' +
|
||||
'not to forecast outcomes.',
|
||||
toneThresholdPct: 30,
|
||||
base: {
|
||||
numStudents: 120,
|
||||
edgesPerNode: 3,
|
||||
triangleProb: 0.45,
|
||||
forwardProb: 0.38,
|
||||
numEducated: 36,
|
||||
origin: 0,
|
||||
graphSeed: 17,
|
||||
thresholdSeed: 2,
|
||||
educationSeed: 1,
|
||||
},
|
||||
panels: [
|
||||
{ id: 'preset-no-program', label: 'No program', strategy: StrategyNone, overrides: {} },
|
||||
{
|
||||
id: 'preset-random',
|
||||
label: 'Educate 30% at random',
|
||||
strategy: StrategyRandom,
|
||||
overrides: {},
|
||||
},
|
||||
{
|
||||
id: 'preset-most-connected',
|
||||
label: 'Educate best-connected 30%',
|
||||
strategy: StrategyMostConnected,
|
||||
overrides: {},
|
||||
},
|
||||
],
|
||||
}
|
||||
26
web/src/presets/types.ts
Normal file
26
web/src/presets/types.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import type { Config, Strategy } from '@/types/engine'
|
||||
|
||||
// Frontend-only presentation types (spec section 1). They describe study
|
||||
// copy and the initial scenario set, not simulation data: Config, Result
|
||||
// and Strategy stay imported from the generated types.
|
||||
|
||||
export interface PanelSpec {
|
||||
id: string // stable client id
|
||||
label: string // user editable
|
||||
strategy: Strategy
|
||||
overrides: Partial<Config> // sparse diff against the base config
|
||||
}
|
||||
|
||||
export interface StudyPreset {
|
||||
headline: string // hero h1
|
||||
// Hero paragraph; {0} {1} {2}... placeholders are replaced by each
|
||||
// panel's reached%, formatted by the shared formatPct.
|
||||
narrative: string
|
||||
disclaimerShort: string // badge text
|
||||
disclaimerLong: string // About popover body
|
||||
toneThresholdPct: number // reached% <= threshold renders "good" (teal)
|
||||
base: Config // the default world
|
||||
panels: PanelSpec[] // initial panels
|
||||
}
|
||||
|
||||
export const MAX_PANELS = 6
|
||||
Loading…
Add table
Add a link
Reference in a new issue