Reference — The DPL (Dynamic Prompt Language) — mockup analysis
Status: design / not yet built. This page captures a full reading of the two DPL mockups in
assets/mockup/ and the JS generator model they are meant to replace, as the starting point for actually
building the language. Nothing here is implemented yet — it is the analysis that precedes a parser/compiler.
The concrete, expanded language proposal that grew out of this analysis is in
dpl-design.md.
The DPL is a proposed small textual scripting language for authoring {#name} dynamic prompts, so a
prompt author doesn't have to hand-write JavaScript (export default function … _.random(…) …). The idea is
old: the 2023-01-21/22 work sketched "a mockup of a simpler custom scripting language … may or may not be
implemented" (see ../version/2023-01.md) — it never was before the project went
dormant. The two files now in assets/mockup/ are that sketch, in two revisions. This is the language a DPL
file would compile down to the same fragment-string a JS generator returns today (see the JS model in
dynamic-prompts.md and the runtime sigils in prompt-dsl.md).
The two mockups, and what they encode
Both mockups are textual rewrites of existing v2 generators, so they are a Rosetta Stone — the same generator in two forms, DPL on the left, JS on the right.
| Mockup | Encodes | JS equivalent |
|---|---|---|
mockup-of-dpl-language.txt |
a beach scene (winter / tropical variants) | data/dynamic-prompts/v2/scene/beach.js |
mockup-of-dpl-language2.txt |
a cave scene (sea / lava / ice / crystal types) | data/dynamic-prompts/v2/scene/cave.js |
Revision 1 — mockup-of-dpl-language.txt (beach)
====================
=- description: Generates a prompt for a beach with a city near it
=- full prompt
====================
====================
winter-beach:
====================
winter beach
* snowy beach
* (#ice)
====================
tropical-beach:
====================
tropical beach
* palm trees
*fail palm tree
====================
start:
====================
beach
*25% +winter-beach
*fail +tropical-beach
* ocean
* oceanside
* seaside
... (more * lines)
* {size} waves
* #eerie
*20% #mystical
* #nature
#city, #weather
Reading it against beach.js line-for-line:
- A header block delimited by
====lines carries metadata:=- description: …(the editor-tooltip sidecar) and a bare=- full promptflag (=export const full = true;). - Named sections are introduced by
name:inside a====banner.start:is the entry point (the module'sdefaultfunction); the others (winter-beach:,tropical-beach:) are local subroutines — equivalent to thewinterBeach()helper function inbeach.js. - The first line(s) of a section, written plain (no
*), are the unconditional base —"beach","winter beach","tropical beach". This is the START layer (see below). - A
*line is an optional clause, default 50% probability →if (_.random(0,1,true) < 0.5) prompt += ", …". The body is appended comma-joined. *NN%overrides the probability:*25%= 25%,*20%= 20% (< 0.25/< 0.2in JS).*failis the else branch of the immediately preceding probabilistic line — it runs only when that roll failed.*25% +winter-beach/*fail +tropical-beach⇒if (roll<0.25) winterBeach(); else tropicalBeach();. (Inbeach.jsthe else-branch is a small inline block; the mockup factors it into a namedtropical-beach:section.)+namecalls/inlines another local section (subroutine reference).+winter-beach⇒winterBeach().#nameembeds a dynamic-prompt generator — the same{#name}token, written bare here.#eerie,#mystical,#nature,#city,#weather.{name}is a list / wildcard pull, exactly the runtime{list}sigil —{size}.(…)wraps output in parentheses (SD weight-grouping).(#ice)⇒ the({#ice})inwinterBeach().- The trailing bare line with no
*—#city, #weather— is the unconditional END layer: always appended (⇒prompt += ", {#city}, {#weather}";).
Revision 2 — mockup-of-dpl-language2.txt (cave)
====================
=- description: Generates a prompt for a cave of different types and seasons
=- full prompt
====================
====================
select 1 cave-type:
====================
sea cave, #underwater
lava cave, #lava
ice cave, #ice
crystal cave, #crystal
====================
start:
====================
cave, cave walls
* subterranean
* interior
* +cave-type
* cavern
... (more * lines)
* #color crystal
* #color gemstone
...
* {color}
#nature, #wildlife, #water, #eerie, #mystical, #weather
What revision 2 adds / changes:
select N name:— a new section kind: a pick-one(-of-N) group. Each line is a candidate; the group emitsNrandomly chosen line(s).select 1 cave-type:⇒ theswitch (_.random(0,4))incave.jsthat picks one ofsea cave/{#underwater},lava cave/{#lava},ice cave/{#ice},crystal cave/{#crystal}. This is the DPL spelling of the codebase's existing pick-one group concept (folders with 2+ generators,.groupfiles — seeprompt-dsl.md), but inline within one file.* +cave-typeshows aselectgroup is invoked like any other section via+name, and can itself be gated by a*roll (the cave switch is behind a 50% gate in JS).- Spacing is looser (
* foovs* foo) — whitespace after the marker is not significant; the*,*fail,*NN%,+,#,{}tokens are. - A clause line can mix a
#nameembed with literal text:#color crystal,#color gemstone⇒", {#color} crystal",", {#color} gemstone".
So the two revisions agree on the core grammar; rev 1 expresses alternation via *fail chains + named
sections, rev 2 adds the select N group as a cleaner pick-one. A real DPL would likely want both.
Deduced grammar (union of both mockups)
| DPL construct | Meaning | JS / runtime equivalent |
|---|---|---|
==== … ==== banner |
section / header delimiter | (structural only) |
=- key: value |
metadata (description) |
<name>.json sidecar |
=- full prompt |
marks the file a full scene | export const full = true |
name: |
named section (subroutine) | a JS function |
start: |
entry section | export default function |
select N name: |
pick-one(-of-N) group section | switch/_.sample over candidates |
| plain line (top of section) | unconditional base (START) | let prompt = "…" |
* text |
~50% optional clause | if (rand < 0.5) prompt += ", text" |
*NN% text |
NN% optional clause | if (rand < 0.NN) … |
*fail text |
else-branch of the previous clause | else … |
| trailing plain line (END) | always appended | prompt += ", …" |
+name |
call/inline a local section | helperFn() |
#name |
embed a dynamic prompt | {#name} token |
{name} |
list / wildcard pull | {list} token |
(…) |
parenthesize (weight group) | (…) in the emitted string |
Full vs partial — the model the DPL must preserve
This is the classification the whole catalog turns on, and the structural pattern the user identified
(start / middle / end layering + balancing). Note: start/middle/end describes the v2 authoring habit,
not a rule of the language — v3 will be structured differently, and the proposed DPL
(dpl-design.md) does not enforce an anchor/tail; lines just run in document order. Authoritative JS detail is in
dynamic-prompts.md; the
shape:
A full prompt is a complete, self-standing scene; a partial is a building-block fragment that garnishes
other prompts. export const full = true (DPL: =- full prompt) is the marker. promptFilesAndSuggestions.js
reads it to split the catalog into "Full Dynamic Prompts" vs "Partial Dynamic Prompts" (the web token picker
and the {#random}/promptSuggestion() engine). v1 and user/ generators are always treated as full;
suggestion_exclude keeps a valid full out of random suggestions.
A full generator (beach.js, cave.js, city.js, …) is built by probabilistic accretion in three
layers:
- START — the anchor. One unconditional base phrase naming the subject:
"beach","cave, cave walls","city, streetview, {city}". Always present; everything else accretes onto it. - MIDDLE — the balanced body. A run of independent optional clauses, each a separate coin-flip
(mostly 50%, some weighted 25%/20%, some mutually-exclusive via
*failchains or aselectgroup). The balancing is exactly this independence: with ~N clauses at p≈0.5, on average ~N/2 appear, but which ones vary every run — so output is richly varied yet never lopsided, and no single clause dominates. Weighted clauses (25%/20%) deliberately make rarer features rare;*fail/selectenforce "pick one of these alternatives, not several." - END — the unconditional context. A trailing always-appended set of other dynamic prompts that
place the scene in an environment:
#city, #weather(beach);#nature, #wildlife, #water, #eerie, #mystical, #weather(cave). After the generator returns, the engine additionally auto-appends{#fx}and{#artists}(whenautoAddFx/autoAddArtistsare on and not already present) — the style layer that finishes a full prompt. (v1 fulls bake fx/artists in themselves and force the auto-append off.)
A partial (ice.js, color, nature, weather, eerie, mystical, …):
- has no
fullflag (DPL: no=- full prompt); - starts from an empty string (
let prompt = "") and emits only", fragment"clauses — it is all MIDDLE, no anchor and no trailing context, so it slots cleanly into a parent's comma list; - is the unit that fulls compose from (the END layer of
cave.jsis literally a chain of partials), and the garnishprePrompt()sprinkles into suggestions.
So the DPL's full/partial distinction is structural, not just a flag: a full file has a real START base +
a trailing END context line; a partial file is a body of * clauses with an empty base and no trailing
context. The =- full prompt header makes it explicit for the classifier and UI.
The randomization envelope (emphasis, editing, alternating, chaos scaling, keywordRepeater) is applied
later by the list/randomization stages, not by the generator/DPL — see
prompt-dsl.md. The DPL only decides which clauses appear and what
text/tokens they contribute.
Open questions for building the DPL (gaps the mockups leave)
The mockups are deliberately incomplete; these are the decisions a parser/compiler design has to settle:
*failscope. Is*failstrictly the else of the single preceding clause, or can it chain (*/*fail/*failas if/elseif/else)? Both mockups only show one*failper clause.select Nfor N>1: distinct picks or with-replacement? Order preserved? Interaction with a*gate on the+ref(rev 2 gates the whole group at 50%).- Probability of base/
select/END lines — confirmed unconditional, but is there a syntax to gate the END line, or to give the base a probability? - Literal escaping — how to emit a literal
#,{,(,*, or a leading=(none of the mockups need it). - Comments, blank-line significance (currently blank lines appear cosmetic), and whether
descriptioncan be multi-line. - File identity / location — extension (
.dpl?), where DPL files live vs.jsgenerators, and whether they coexist (a loader that reads both) or DPL compiles to JS at build time. - Compile target — interpret a DPL AST at runtime inside
dynamicPrompt.js, or transpile to the existingexport default functionshape so nothing downstream changes. The latter keeps the whole classifier / suffix-resolution / gating machinery untouched. - NSFW gating,
-v1/usernamespaces,_force-prefix,.groupmarkers — how the DPL surfaces (or inherits) the name-token gating and folder conventions that already govern the JS catalog. - Variants — whether the DPL exposes
-sfw/-nsfwor{#any}semantics, or leaves those to the engine.
Where each piece lives
| Concern | Location |
|---|---|
| The two mockups | assets/mockup/mockup-of-dpl-language{,2}.txt |
| JS generators the mockups encode | data/dynamic-prompts/v2/scene/{beach,cave}.js |
| Full/partial classifier | src/promptFilesAndSuggestions.js |
| Runtime sigils / pipeline | prompt-dsl.md, src/core/stages/dynamicPrompt.js |
| JS authoring idiom & catalog | dynamic-prompts.md |
| Original 2023 mockup history | ../version/2023-01.md |