PURPOSE
Playground tab that controls hub/spoke pattern generation, terrain settings, enemy spawning, and a build mode for placing terrain blockers and triggers. Instance-side (left panel) edits affect the current test run only; base-side (right panel) edits rewrite the shared level-presets.ts source via the playground push pipeline.
OWNS
- Section open/closed state:
patternOpen,spawnOpen,buildOpen. - Spawning runtime knobs not stored in
LevelConfig:spawnerOn,spawnRate,knobs(enemyHpMult / enemyDamageMult / enemySpeedMult / enemyCountMult). - Build mode UX state:
buildSubMode(terrain|trigger),selectedTriggerType,triggerRadius. - Debug toggles:
zoneDebug,wireframe. - Latest level-generation
statssnapshot returned by the mission. LevelPresetEditor(nested component) ownsselectedIdand a per-presetdraftsmap of pendingPartial<LevelConfig>patches.
READS FROM
DEFAULT_LEVEL_CONFIGfrom../../data/level-configas the fallback when nolevelConfigprop is supplied.LEVEL_PRESETS,LEVEL_PRESET_NAMES,LevelPresetIdfrom../../data/level-presets.- Types
LevelConfig,PatternType,TerrainTypeId,TriggerTypefrom../../data/level-config. Panel,StatRow,PushButton,BTN_STYLE,LABEL_STYLE,TabPropsfrom./PlaygroundShared.missionRef.current?.getCameraTransform()to anchor placed terrain and triggers at the camera position.missionRef.current?.sandboxGetLevelStats?.()for the post-regen stats readout.missionRef.current?.sandboxGetTerrainCount()andsandboxGetTriggerCount()for undo and counter UI.
PUSHES TO
setLevelConfig(setLc) prop — instance-side slider/select edits patch the activeLevelConfigvia aset(patch)helper.setBuildMode,setSelectedShapeprops — bubble build-mode UI selections up to the playground host.- Mission ref methods on the active run:
setSpawnerEnabled(boolean)sandboxSetSpawnRate(value)setWorldKnobs(knobs)sandboxSetLevelConfig(config)(only on preset load or REGENERATE WORLD click)sandboxSetZoneDebug(next)sandboxSetHubSpokeWireframe(next)sandboxPlaceTerrain(shape, x, y, size, rotation)sandboxRemoveTerrain(index)sandboxAddTrigger({ id, type, x, y, radius, oneShot, payload })sandboxUndoTrigger()clearEnemies()
pushStatsfrom../../services/playgroundPushwithtarget: 'level-preset'to rewritelevel-presets.tsfrom the base-side editor.
DOES NOT
- Does not regenerate the world automatically on slider changes. Pattern, terrain, and seed edits accumulate in React state until the user clicks REGENERATE WORLD or loads a preset.
- Does not own
LevelConfigstorage —lc/setLcare received as props. - Does not own
buildModeorselectedShape— those are received as props from the playground host with local fallbacks for when they are absent. - Does not edit non-numeric
LevelConfigfields in the base-side editor (notes line states these are edited via source for now). - Does not implement spawn-one controls — comments explicitly direct the user to the ENEMIES tab.
- Does not render a wilds fog slider; comment notes the fog system was deleted because it caused checkerboard grid artifacts.
Signals
- Preset button click in LEVEL GENERATION calls
loadPreset(id)which replacesLevelConfigand immediately invokessandboxSetLevelConfig(preset). - World mode toggle between
precomputedandscriptedswaps the body of the LEVEL GENERATION panel;scriptedmode shows an explanatory stub instead of geometry controls. - Pattern selection (
chaotic,rings,grid,zigzag) gates which geometry sliders render under hub/spoke. randomizeSeedwrites a new integer seed intoLevelConfigbut does not regenerate until REGENERATE WORLD is clicked.fetchStatsposts a 200ms delayed read ofsandboxGetLevelStatsafter a regen click to surface hub/spoke/terrain/event/grid/bake stats.- Spawner ON/OFF chip in the SPAWNING panel header toggles
setSpawnerEnabledand stops event propagation so the panel doesn’t collapse. - Spawn Pressure slider routes through
changeSpawnRatewhich mirrorsspawnRatestate andsandboxSetSpawnRatein one step. - Knob sliders (HP / Damage / Speed / Max Enemy Scale) all flow through
patchKnobwhich merges into the localknobsobject and callssetWorldKnobswith the merged value. - CLEAR ALL button on SPAWNING calls
clearEnemies(). - BUILD MODE panel shows sub-mode selector (
TERRAIN/TRIGGER) only whenbuildModeis on. - Terrain sub-mode PLACE button reads camera transform and places the selected shape at camera position offset by
+200on x. - Terrain UNDO removes the last terrain entry by index = count − 1.
- Trigger sub-mode PLACE constructs a payload that branches by trigger type:
spawn_waveuses a hard-coded enemy mix (3 shooter_common + 2 orb_common),dialogueuses a hard-coded test string and 3-second duration, others get{}.
Entry points
- Default export
LevelTabis the React component consumed by the playground host (matches theTabPropscontract fromPlaygroundShared). sideprop branches the render:'base'returns the nestedLevelPresetEditoronly; any other value (default'instance') returns the full instance-side panel stack (LEVEL GENERATION + SPAWNING + BUILD MODE).LevelPresetEditoris a private nested component, not exported.
Pattern notes
- Module-scope
PATTERNS,TERRAIN_TYPES,PRESET_IDS, andPRESET_NUMERIC_FIELDSare typed constant arrays used for declarative button/select rendering. setis auseCallback-wrapped patcher that wrapssetLcand falls back silently whensetLcis undefined.- The instance-side panel deliberately decouples slider state from world regeneration — only REGENERATE WORLD or preset load invokes
sandboxSetLevelConfig. This is documented in an inline comment. - The base-side
LevelPresetEditorkeeps a per-preset draft map keyed byLevelPresetIdso switching presets preserves unsaved edits per id; the draft is cleared for the active preset only on a successful push. pushPresetreturns a boolean to satisfyPushButton’s contract; failures swallow the exception and returnfalse.- Build-mode and selected-shape props are defaulted with
?? falseand?? 'blocker_rect'and no-op setters so the component renders safely without a host that wires them up. - Inline styles dominate the file; shared style tokens (
BTN_STYLE,LABEL_STYLE) come fromPlaygroundSharedand per-state colors (orange#f97316for build, blue#60a5fafor selection, green#22c55efor active toggles, purple#a855f7for triggers, cyan#38bdf8for wireframe, violet#7c3aedfor zone debug) are hard-coded at use sites. - The terrain
selectelement stops both React-synthetic and native keyboard propagation to prevent the playground’s global key handlers from intercepting option-list navigation.