PRESENTER DECK, Marp.
npx @marp-team/marp-cli deck.marp.md -o deck.html --allow-local-files (interactive, press p for notes)
npx @marp-team/marp-cli deck.marp.md -o deck.pdf --allow-local-files (clean PDF)
npx @marp-team/marp-cli -p -w deck.marp.md --allow-local-files (live preview)
(--allow-local-files is REQUIRED: the deck embeds assets/screenshot.png)
STRUCTURE: simple → complex. Slides 1-18 = pure Phel basics (no game code).
Slides 19+ = real phel-doom code.
20s. "40 minutes: I'll teach you a Lisp - and prove it's not a toy by showing a DOOM clone I built with it."
Eye contact. No notes.
3s silence. Let it breathe. Say nothing. Let the room realize what they're looking at.
Then click to the claim.
Click in. 3s silence. Let the GIF breathe.
"DOOM. In a terminal. Written in Lisp. Compiled to PHP 8.4."
Name the room: "You're thinking: that can't work in PHP."
30s. "Four things: why it exists, how to write it, how it plugs in, when to use it."
Don't read the list. Signpost and move.
Clojure folks: "Same idea, PHP runtime." Everyone else: "PHP under the hood, written like a Lisp."
Land on: "you require a dependency, not a platform."
"You write Lisp. You ship PHP. Server learns nothing new."
Tease: "We'll open the compiled files live later - no magic."
Zero Lisp experience assumed. All examples standalone, no game code yet.
"If you can read (+ 1 2), you can read every line in this game."
Tackle parens: "Yes, parentheses. You stop seeing them in ten minutes."
Don't dodge it - name it, defuse it, move.
"? suffix - convention, like PHP's is_ prefix. Not special syntax."
"let is a scoped block. a, b, total vanish after the closing ]."
"No return keyword. Last expression IS the value."
"defn = public, defn- = private. That's the whole access model."
"#() is shorthand for a one-liner fn. % is the argument. You'll see it everywhere."
PHP translation: "map = assoc array, vector = indexed array, set = unique values."
":name is a keyword - interned, faster to compare than a plain string."
"map, filter, reduce - you already know these. Same concept, cleaner syntax."
"for is like a list comprehension. :when is the filter condition."
"if and cond return a value. No temp variable needed."
Walk grade(82) aloud → :B. One beat. Move.
"PHP fluent chains need objects. -> works on any value."
Give this a full breath - most useful daily concept.
"loop = initial state. recur = next iteration, no new stack frame."
Foreshadow: "Same shape casts one ray per screen column, ~120-180 a frame in the raycaster. Coming up."
"Functional doesn't mean no state. 11,700 lines, a handful of atoms."
"swap! applies a function atomically. @ reads the current value."
Bridge from Phel concepts to their PHP world.
"Every PHP function, prefixed with php/. There is no wall."
"Objects: php/new to construct. .method to call. ClassName/CONST for statics."
Segue: "Next slide: the REPL - let's try it live."
DO IT LIVE. 3-4 expressions. Invite: "throw me something."
Breather - don't rush. 90s max, then back to slides.
"phel doctor checks your setup. phel format auto-formats. phel lint catches errors early."
"PhpStorm and VS Code have first-class plugins - syntax, REPL actions, LSP + nREPL."
"Phel joins your workflow, not replaces it."
Real project code. They know the primitives. Now see them at scale.
"ns dot-separated mirrors the file path - same idea as PSR-4 namespaces."
"*build-mode* stops top-level effects during phel build. First clue about compilation."
VERY high level - don't read code. "main wired the CLI; play IS the game: a loop."
Trace the cycle with your finger: input → tick (pure) → render (io) → back to the top with a NEW world.
"Hold this picture. Next: what's actually IN a world? Then: how one frame transforms it."
"Everything you see, shoot, or pick up is in this one map."
Point at :grid, :player, :enemies. "Diff it. Save it. Replay it."
"PHP: class, constructor, getters. Phel: 4-line function, plain map."
Pause after the image. That contrast is the laugh.
"Physics, pickups, enemies, projectiles, shooting, damage - each just world→world."
"Nothing can corrupt anything else. All testable without a terminal."
"core/ literally cannot call print or rand. No require, no access."
DO LIVE: tree src/ - point at the three directories.
SET UP the real slide. Say it plain BEFORE the code.
"Forget 3D engines. A maze seen from above, drawn one column at a time."
Gesture: hands wide for tall=close, narrow for far. Then: "now the real version."
DEMO > bare raycaster (split layout: left 3D, right 2D map, walls only):
phel run phel-doom.main demo --phase 1
Bridge from the previous slide: "that hand-wave, now a formula."
"This is the entire trick - one division per ray."
"Carmack's constraint: no looking up or down, flat floors per room. That keeps the math simple."
Pause. "Now the code. Same algorithm, in Phel. Loop and recur."
"Same loop/recur from slide 13 - now casting one ray per screen column, 120-180 a frame."
"SIMPLIFIED: step-march. Real engine uses DDA." Say it once, move on.
Energy: "DOOM 1993. In a terminal. Written in Lisp."
DEMO > build it up live, same engine, one subsystem at a time:
phel run phel-doom.main demo --phase 2 ; + the pistol
phel run phel-doom.main demo --phase 3 ; + enemies
phel run phel-doom.main demo --phase 4 ; + interior cover walls
"Macros rewrite code at compile time. This is the humblest use - zero-cost inlining."
Trace buf-set → php/aset. "A function would pay a call per cell; this pays nothing."
One beat. Pause.
"Every pure function is a test waiting to happen."
"What you'd normally mock: clock, RNG, renderer. Nothing to mock here."
Point at chart: 2.04 → 0.51 ms.
"Memoize was one line. Legal because it's pure - same input, same output."
Honest: "Persistent vectors are beautiful. ~680x slower in a 7,000-cell hot loop."
"Same seed → same levels, same enemies, same loot. Entire run is reproducible."
If time: DO LIVE - run --demo replay.
DO LIVE: less out/phel_doom/core/state.php
"No magic. PHP you'd recognize. ??= interns keyword once. defn → AbstractFn + __invoke."
BEFORE TALK: run phel build so the file is fresh.
Rising energy. Story → verdict → demo.
"Not a big bang. One issue, one PR, every day."
"Pure architecture enabled this - each feature bolted on without breaking anything."
Sweep fast. Rising energy into demo.
"Enough slides. Let me show you."
"Start on a pure-logic module. Don't rewrite your framework."
Candor here buys credibility. Don't rush - last word before demo.
Deep breath. Terminal is the star now.
~5 min. Narrate: "every frame, a brand-new immutable world."
End on F3: "real budget - callback to slide 28."
BEFORE TALK: terminal font BIG. Boss command in shell history. Fallback video loaded.
Leave REPL running on screen.
If pause: "We covered why, how to write it, how it integrates, when to use it."
Link stays up. Let people scan or type it.