it works now

This commit is contained in:
Jeremy Penner 2010-12-01 17:04:15 -08:00
parent bbfa2addc9
commit 8be8459d9f
5 changed files with 595 additions and 92 deletions

View file

@ -1,30 +1,33 @@
TIME TRAVEL:
actions -- things that the player has explicitly done. They generate nevs, but are not necessarily the only source of them.
nev -- narrative event -- a moment in time. nevs contain some text to display to the user to communicate what is happening, some handles with which she may generate actions, and some consequences to the state of the world.
CHANGING THE PAST:
in my game, a nev can be inserted in the past as a result of a later action, in direct violation of causality. I do this because it is interesting.
ways we could deal with this:
- try to calculate a stable loop to determine whether or not the action is legal
- cons: hard for the author to reason about (?), hard to write code to deal with, high time complexity
- split the timeline
- cons: the player may be able to perform an action which has consequences in the past which will prevent him from performing the action. The player will then see the consequence but not the action.
- model this problem as inserting an action at the earlier point to begin with
- pros: the system should probably be able to model things this way anyway
causality is not violated
- cons: maybe somewhat fragile from the author's POV
how to specify where the nev goes?
UI for undoing should be on the nev following the player's actual action, not the modelled one
same problems as timeline splitting, actually -- just giving the author the tool rather than baking it into the engine
these are important questions regardless of whether we implement things this way or not though
OK
ACTIONS:
- happen in response to nevs!
- nevs have [generated?] stable IDs, and appear at most once in the text. (nevs can also be parameterized?)
MULTIPLE ACTORS:
- soooo it makes sense to have multiple actors generating actions! the player-character is merely one such actor, with a simple "AI".
- other actors can generate nevs according to other criteria.
- so we could model a TimeLord actor which generates a nev in response to the player's future action? No, because the only way to tell if future actions are valid is to simulate the world!
- could provide a world-simulating primitive that action validity calls could run
TIME TRAVEL:
actions -- things that the player has explicitly done. They generate nevs, but are not necessarily the only source of them.
nev -- narrative event -- a moment in time. nevs contain some text to display to the user to communicate what is happening, some handles with which she may generate actions, and some consequences to the state of the world.
CHANGING THE PAST:
in my game, a nev can be inserted in the past as a result of a later action, in direct violation of causality. I do this because it is interesting.
ways we could deal with this:
- try to calculate a stable loop to determine whether or not the action is legal
- cons: hard for the author to reason about (?), hard to write code to deal with, high time complexity
- split the timeline
- cons: the player may be able to perform an action which has consequences in the past which will prevent him from performing the action. The player will then see the consequence but not the action.
- model this problem as inserting an action at the earlier point to begin with
- pros: the system should probably be able to model things this way anyway
causality is not violated
- cons: maybe somewhat fragile from the author's POV
how to specify where the nev goes?
UI for undoing should be on the nev following the player's actual action, not the modelled one
same problems as timeline splitting, actually -- just giving the author the tool rather than baking it into the engine
these are important questions regardless of whether we implement things this way or not though
OK
ACTIONS:
- happen in response to nevs!
- nevs have [generated?] stable IDs, and appear at most once in the text. (nevs can also be parameterized?)
MULTIPLE ACTORS:
- soooo it makes sense to have multiple actors generating actions! the player-character is merely one such actor, with a simple "AI".
- other actors can generate nevs according to other criteria.
- so we could model a TimeLord actor which generates a nev in response to the player's future action? No, because the only way to tell if future actions are valid is to simulate the world!
- could provide a world-simulating primitive that action validity calls could run
GAME IDEAS:
"I'm thinking of a number"

450
iffy.coffee Normal file
View file

@ -0,0 +1,450 @@
###
Iffy - parserless interactive fiction for the web
(c)2010 Jeremy Penner
###
#lazy enumeration
class Eni
constructor: (@val, @fStarted) ->
Value: () -> @val
EniNext: () -> if not @fStarted then new Eni(@val, true)
EniEmpty = { EniNext: (-> null) }
class EniArray
constructor: (@rg, @i) -> if not @i? then @i = -1
Value: -> @rg[@i]
EniNext: -> if @i + 1 < @rg.length then new EniArray(@rg, @i + 1)
class EniConcat
constructor: (@eni1, @eni2) ->
Value: -> @eni1.Value()
EniNext: (rgarg...) ->
eni1New = @eni1.EniNext(rgarg...)
if eni1New?
new EniConcat(eni1New, @eni2)
else
@eni2.EniNext(rgarg...)
class EniThunk
constructor: (@thunk) ->
# it is always illegal to call Value on this eni, as eninext will never return an eniThunk
EniNext: (rgarg...) ->
# memoize generated eni
if not @eni?
@eni = @thunk(rgarg...)
@eni.EniNext()
class EniCons
constructor: (@value, @thunk, @fStarted) ->
Value: -> @value
EniNext: (rgarg...) ->
if not @fStarted
if not @eniNext?
@eniNext = new EniCons(@value, @thunk, true)
@eniNext
else
if not @eni?
@eni = @thunk(this, rgarg...)
@eni.EniNext()
class EniMap
constructor: (@eni, @dg) ->
Value: -> @dg(@eni.Value())
EniNext: (rgarg...) ->
eniNext = @eni.EniNext()
if eniNext? then new EniMap(eniNext, @dg, rgarg...)
class EniFilter
constructor: (@eni, @dgFilter) ->
Value: -> @eni.Value()
EniNext: (rgarg...) ->
eni = @eni.EniNext(rgarg...)
while eni? and not @dgFilter(eni.Value(), eni, rgarg...)
eni = eni.EniNext()
eni
ArrayFromEni = (eni) ->
rg = []
while (eni = eni.EniNext())?
rg.push(eni.Value())
rg
# world state -- holds the state of the world after every
NewWst = (obj, gst) ->
Ctor = (@gst) ->
@WstNext = (nev) ->
CtorNext = (wstOld) ->
@nev = nev
@wstPrev = wstOld
this
CtorNext.prototype = this
new CtorNext(this)
@WstPrev = () -> @wstPrev
this
Ctor.prototype = obj
new Ctor(gst)
class Respo
constructor: (@enieniNev, @eniNev) ->
if @enieniNev.eni.i < 0 and @eniNev?
@eniNev = @eniNev
@FromEnieniNev: (enieniNev) -> new Respo(enieniNev)
Nev: () -> @eniNev.Value()
Next: (nevgen) ->
enieniNev = @enieniNev
eniNev = @eniNev
loop
if not eniNev?
enieniNev = enieniNev.EniNext()
if enieniNev?
eniNev = enieniNev.Value()
else
return null
eniNev = eniNev.EniNext(nevgen, new Respo(enieniNev, eniNev))
if eniNev? then return new Respo(enieniNev, eniNev)
null
ReplaceEniNev: (eniNevNew) -> new Respo(@enieniNev, eniNevNew)
class Nevgen
constructor: (@gst, @wst, @eniWstResponding, @respo) ->
@EniWst: (gst, wstInit) ->
nevgen = new Nevgen(gst, wstInit, null, null)
new EniCons(wstInit, nevgen.Thunk())
Thunk: () ->
(eniPrev) =>
nevgen = this
if not @eniWstResponding?
nevgen = @RespondTo(@gst, @wst, eniPrev)
nevgen = nevgen.Next()
if nevgen?
new EniCons(nevgen.wst, nevgen.Thunk())
else
EniEmpty
Next: () ->
nevgen = this
while nevgen? and nevgen.wst == @wst
nevgen = nevgen.NextI()
nevgen
NextI: () ->
if @respo?
respoNext = @respo.Next(this)
wst = @wst
if respoNext? and respoNext.Nev().FCanRun(@gst, wst)
wst = @gst.RunNev(wst, respoNext.Nev())
new Nevgen(@gst, wst, @eniWstResponding, respoNext)
else if @wst != @eniWstResponding.Value()
@RespondTo(@gst, @wst, @eniWstResponding.EniNext())
RespondTo: (gst, wst, eniWstResponding) ->
new Nevgen(gst, wst, eniWstResponding, gst.story.RespoResponse(eniWstResponding.Value().nev, wst))
# game state -- holds all the information needed to display the current state of the game in our browser
class Gst
constructor: (@story, @jDiv) ->
@wstInit = @story.WstInit(this)
@igenID = 0
Display: () ->
@mpstId_dgOnClick = []
@ClearMenu(true)
@jDiv.empty()
@jDiv.click(() => @ClearMenu())
@jDiv.append("<div class='title'>
<h1>#{@story.jStory.attr('title')}</h1>
<h2>#{@story.jStory.attr('author')}</h2>
</div>")
for wst in @RgwstRun()
@jDiv.append($("<div class='nev'/>").append(wst.nev.StHtmlDisplay(this, wst)))
for stId_dgOnClick in @mpstId_dgOnClick
@jDiv.find("##{stId_dgOnClick[0]}").click(stId_dgOnClick[1])
ShowMenu: (ev, dLink, rgverb) ->
@ClearMenu(true)
jMenu = $("<div class='iffy-menu'/>").hide()
for verb in rgverb
jMenuItem = $("<a href='javascript:void(0)'>#{verb.stDisplay}</a><br/>").click(() => verb.dgActivate(); @Display())
jMenu.append(jMenuItem)
jMenu.css({
position: "absolute"
top: ev.pageY
left: ev.pageX
})
@jDiv.append(jMenu)
@jMenu = jMenu
@fMenuVisible = false
jMenu.show('blind', {}, 300, () => @fMenuVisible = true)
ClearMenu: (fForce) ->
if @jMenu? and (fForce or @fMenuVisible)
@jMenu.hide('blind', {}, 300, () -> $(this).remove())
@jMenu = null
@fMenuVisible = false
RegOnClick: (dgOnClick) ->
stId = "__gen#{@igenID++}"
@mpstId_dgOnClick.push([stId, dgOnClick])
stId
RgwstRun: () ->
wst = @wstInit.WstNext(@story.NevByName("start"))
eniWst = Nevgen.EniWst(this, wst)
ArrayFromEni(eniWst)
RunNev: (wst, nevResponse) ->
wst = wst.WstNext(nevResponse)
nevResponse.RunAction(this, wst)
wst
FWillNevRun: (wst, eniNevResponseNext, nevgen, respo, nevPretend, nevLater) ->
wstNext = @RunNev(wst, nevPretend)
ThunkWstContinue = (eniWstRespondingOld) ->
() ->
if eniWstRespondingOld?
if eniWstRespondingOld.Value() == wst
return new EniCons(wst, ThunkWstContinue(null))
else
return new EniCons(eniWstRespondingOld.Value(), ThunkWstContinue(eniWstRespondingOld.EniNext()))
else
return new EniConcat(new Eni(wstNext), eniWstNextPre)
eniWstRespondingNew = ThunkWstContinue(nevgen.eniWstResponding)().EniNext()
eniWstNextPre = new Nevgen(this, wstNext, eniWstRespondingNew, respo.ReplaceEniNev(eniNevResponseNext)).Thunk()()
eniWstNext = eniWstNextPre.EniNext()
while eniWstNext?
if eniWstNext.Value().nev.ID() == nevLater.ID()
return true
eniWstNext = eniWstNext.EniNext()
return false
FilterStHtml: (stHtml, wst) ->
for actor in @story.rgactor
try
stHtml = actor.FilterStHtml(stHtml, wst)
catch error
stHtml = stHtml + "<p class='error'>[[error interpreting markup: #{error}]]</p>"
stHtml
# narrative event -- the story is told as a sequence of these. Contains the HTML to display when the
# narrative event happens, the preconditions to make sure the event should be allowed to occur, and
# the resultant effect on the state of the model of the world
class Nev
constructor: (@dNev) ->
ID: () ->
if not @id?
rgstName = $(@dNev).parents("[name]").map(() -> $(this).attr("name")).get()
rgstName.reverse()
rgstName.push($(@dNev).attr("name"))
@id = rgstName.join(".")
@id
FHasRun: (wst) ->
while wst?.nev
if wst.nev.ID() == @ID()
return true
wst = wst.WstPrev()
return false
FCanRun: (gst, wst) -> not @FHasRun(wst)
RunAction: (gst, wst) ->
StHtmlDisplay: (gst, wst) ->
jdivTmp = $("<div/>")
for dHTML in $(@dNev).contents()
jdivTmp.append(document.importNode(dHTML, true))
stHtml = jdivTmp[0].innerHTML
gst.FilterStHtml(stHtml, wst)
#[word text to display] -- links to a word, usually a noun, that the player can interact with in some way.
#[word] == [word word]
# $[if (expr)]some text$[else]some other text$[endif] (else is optional)
# $[(expr)] inserts as a string the result of expr
#[[ outputs [, ]] outputs ]
TemplateFromStNev = (st, wst) ->
class Xpd
constructor: (@st, @fExpr) ->
JsStringLit = (st) ->
"'" + st.replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/, "\\t") + "'"
rgxpd = []
ich = 0
ichLim = 0
ichLimTok = 0
PushText = (fExpr, dgSt) ->
stTok = st.substring(ich, ichLim)
if dgSt? then stTok = dgSt(stTok)
if stTok then rgxpd.push(new Xpd(stTok, fExpr))
ich = ichLimTok
ichLim = ichLimTok
Match = (dgPushNoTok, rgre_dg...) ->
ire = 0
ireMatched = -1
ichMatchFirst = null
for ire in [0...rgre_dg.length] by 2
re = rgre_dg[ire]
re.lastIndex = ichLimTok
rgstMatched = re.exec(st)
if rgstMatched?
ichMatchT = re.lastIndex - rgstMatched[0].length
if not ichMatchFirst? or ichMatchT < ichMatchFirst
ichMatchFirst = ichMatchT
stMatched = rgstMatched[0]
ireMatched = ire
reMatched = rgre_dg[re]
if ireMatched >= 0
ichLim = ichMatchFirst
ichLimTok = ichMatchFirst + stMatched.length
rgre_dg[ireMatched + 1]()
else
ichLim = ichLimTok = st.length
if dgPushNoTok?
dgPushNoTok()
null
MatchInText = () ->
EndText = () ->
PushText(true, (st) -> JsStringLit(st.replace(/\[\[/g, "[").replace(/\]\]/g, "]")))
Match(EndText,
/\[\[/g, () -> MatchInText,
/\]\]/g, () -> MatchInText,
/\[/g, () -> EndText(); MatchInWord,
/\$\[/g, () -> EndText(); MatchInExpr)
MatchBracket = (cbracket, dgPush) ->
() -> Match(dgPush, # error?
/"/g, () -> MatchString(MatchBracket(cbracket, dgPush)),
/\[/g, () -> MatchBracket(cbracket + 1, dgPush),
/\]/g, () -> if cbracket == 0 then dgPush(); MatchInText else MatchBracket(cbracket - 1, dgPush))
MatchString = (matchAfter) ->
() ->
Match(null,
/\\\\/g, () -> MatchString(matchAfter),
/\\"/g, () -> MatchString(matchAfter),
/"/g, () -> matchAfter)
StWord = (st) ->
rgstDisplay = /[ ]*([^ ]+)(.*)/.exec(st)
stWord = rgstDisplay[1]
if rgstDisplay[2]
stDisplay = rgstDisplay[2]
else
stDisplay = rgstDisplay[1].replace(/_/g, " ")
word = wst.gst.story.WordByName(wst.gst, stWord)
if word? and (rgverb = word.Rgverb(wst)).length > 0
dgOnClick = (ev) -> wst.gst.ShowMenu(ev, this, rgverb)
stId = wst.gst.RegOnClick(dgOnClick)
JsStringLit("<a href='javascript:void(0)' id='#{stId}' class='iffy-word'>#{stDisplay}</a>")
else
JsStringLit(stDisplay)
MatchInWord = MatchBracket(0, () -> PushText(true, StWord))
MatchInExpr = MatchBracket(0, () -> PushText(true))
BuildRgxpd = (dgMatch) ->
while dgMatch?
dgMatch = dgMatch()
BuildRgxpd(MatchInText)
rgstJs = ["var __p=[];with(obj){"]
fExpr = false
for xpd in rgxpd
if xpd.fExpr != fExpr
rgstJs.push(if fExpr then ");" else "__p.push(")
else if fExpr
rgstJs.push(",")
fExpr = xpd.fExpr
rgstJs.push(xpd.st)
if fExpr then rgstJs.push(");")
rgstJs.push("}return __p.join('');")
new Function("obj",rgstJs.join(''))
class Word
constructor: (@gst, @jWord) ->
Rgverb: (wst) ->
rgverb = []
for dVerb in @jWord.find("verb")
nev = new Nev(dVerb)
if not nev.FHasRun(wst) and not @gst.story.actorPlayer.FWillAttempt(wst, nev)
stDisplay = $(dVerb).attr("display") or $(dVerb).attr("name")
dgActivate = () => @gst.story.actorPlayer.RespondTo(wst.nev, new Nev(dVerb))
rgverb.push(new Verb(stDisplay, dgActivate))
rgverb
class Verb
constructor: (@stDisplay, @dgActivate) ->
# actors drive the story by responding to nevs with more nevs
# ActorPlayer does its best to perform the actions that the player has told the game to perform.
class ActorPlayer
constructor: (@story) ->
@mpnevID_rgnev = {}
RespondTo: (nev, nevResponse) ->
rgnev = @mpnevID_rgnev[nev.ID()]
if rgnev?
rgnev.push(nevResponse)
else
@mpnevID_rgnev[nev.ID()] = [nevResponse]
FWillAttempt: (wst, nev) ->
rgnev = @mpnevID_rgnev[wst.nev.ID()]
if rgnev?
for nevResponse in rgnev
if nevResponse.ID() == nev.ID()
return true
return false
EninevResponse: (nev, wst) ->
rgnev = @mpnevID_rgnev[nev.ID()]
if rgnev? then new EniArray(rgnev) else EniEmpty
FilterStHtml: (stHtml, wst) ->
TemplateFromStNev(stHtml, wst)(wst)
class Story
constructor: (@jStory) ->
@actorPlayer = new ActorPlayer(this)
@rgactor = [@actorPlayer]
NevByName: (stName) ->
if stName?
rgstName = stName.split('.')
for dNev in @jStory.find("[name=#{rgstName[rgstName.length - 1]}]")
istName = rgstName.length - 2
dParent = dNev
while dParent? and istName >= 0
dParent = $(dParent).parent("[name=#{rgstName[istName]}]").get(0)
istName--
if dParent?
return new Nev(dNev)
null
WordByName: (gst, stName) ->
jWord = @jStory.find("word[name=#{stName}]")
if jWord.length > 0 then new Word(gst, jWord)
RespoResponse: (nev, wst) ->
Respo.FromEnieniNev(new EniMap(new EniArray(@rgactor), (actor) -> actor.EninevResponse(nev, wst)))
WstInit: (gst) ->
NewWst({}, gst)
Rgget = (rgurl, dgAfter, iurl, rgresult) ->
if not iurl? then iurl = 0
if not rgresult then rgresult = []
if iurl == rgurl.length
dgAfter(rgresult)
else
# $.get, called with javascript, results in a "syntax error" in FireFox if the result is not xml. This is OK.
$.get(rgurl[iurl], ((result) -> rgresult.push(result); Rgget(rgurl, dgAfter, iurl + 1, rgresult)), "text")
window.PlayStory = (url, jDiv) ->
$.get(url, (domXml) ->
story = new Story($("story", domXml))
gst = new Gst(story, jDiv)
rgurlExt = []
for domLoad in $("loadmodule", domXml)
rgurlExt.push($(domLoad).attr("url"))
Rgget(rgurlExt, (rgscript) ->
for script in rgscript
eval(script)(gst)
gst.Display()
)
, "xml")

35
retrochronal.coffee Normal file
View file

@ -0,0 +1,35 @@
###
RetroChronal -- an iffy module for violating causality
Usage:
<pastresponse after="nev" future_cause="nev" [name="name"]>
nev text
</pastresponse>
where the nev specified in "after" is the one that this nev should appear after, and the nev specified in future_cause is the one
that causes this nev to happen in the future. If the nev specified in future_cause doesn't appear, this nev will not be displayed.
###
class ActorRetro
constructor: (@story) ->
@mpnevID_rgnevResponse = {}
for dResponse in @story.jStory.find("pastresponse")
@add(@story.NevByName($(dResponse).attr("after")), dResponse)
add: (nev, dResponse) ->
if not @mpnevID_rgnevResponse[nev.ID()]?
@mpnevID_rgnevResponse[nev.ID()] = []
@mpnevID_rgnevResponse[nev.ID()].push(new Nev($(dResponse)))
EninevResponse: (nev, wst) ->
rgnevTest = @mpnevID_rgnevResponse[nev.ID()]
if rgnevTest?
FKeepNevTest = (nevTest, eniNev, nevgen, respo) ->
nevFuture = nevgen.wst.gst.story.NevByName($(nevTest.dNev).attr("future_cause"))
return nevFuture? and nevgen.wst.gst.FWillNevRun(nevgen.wst, new EniFilter(eniNev, FKeepNevTest), nevgen, respo, nevTest, nevFuture)
new EniFilter(new EniArray(rgnevTest), FKeepNevTest)
else
EniEmpty
FilterStHtml: (stHtml, wst) -> stHtml
return (gst) ->
gst.story.rgactor.splice(0, 0, new ActorRetro(gst.story))

View file

@ -1,15 +1,31 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="iffy.js"></script>
<script type="text/javascript">
$(document).ready(function () {
PlayStory("timemachine.xml", $("#story"));
});
</script>
</head>
<body>
<div id="story" />
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<link rel="stylesheet" href="jqueryui/css/ui-lightness/jquery-ui-1.8.5.custom.css">
<style>
div.iffy-menu {
border: 1px solid #777;
position: absolute;
background-color: #eeeeee;
}
a.iffy-word:link {
text-decoration: none;
color: #000000;
}
a.iffy-word:hover {
color: #0000dd;
}
</style>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jqueryui/js/jquery-ui-1.8.5.custom.min.js"></script>
<script type="text/javascript" src="iffy.js"></script>
<script type="text/javascript">
$(document).ready(function () {
PlayStory("timemachine.xml", $("#story"));
});
</script>
</head>
<body>
<div id="story" />
</body>
</html>

View file

@ -1,47 +1,46 @@
<story title="Richard and Larry Build A Time Machine" author="Jeremy Penner">
<nev name="start">
<p>"I still can't believe you actually used a [DeLorean]," says Larry.</p>
<p>"Hey, if you're going to do a thing, you ought to do it right," says [Richard].</p>
</nev>
<word name="DeLorean">
<description>
Larry looks at the DeLorean with astonishment. Richard has done it up to look <i>exactly</i> like the car from
<i>Back to the Future</i>.
</description>
<verb name="Drive">
<p>Larry eyes the car. "Give me the keys," he says. "I want to take this baby to 88."</p>
<p>"Oh, it doesn't drive anymore," says Richard. "I had to use the engine to power the time machine."</p>
</verb>
</word>
<word name="Richard">
<verb name="how" display="What have you done?">
<p>"So, run it by me again," says Larry.</p>
<p>"I modified this [DeLorean] to send information backwards through time," says [Richard].</p>
<p>"Just information. Not matter."</p>
<p>"Right."</p>
<p>Larry ponders this for a moment. "How... how does that even work?"</p>
<p>[Richard] waves his hands around. "Spooky action at a distance," he says.</p>
<p>"No, I mean, augh. I mean, what can you practically change about the past with this machine?" says Larry.</p>
<p>
"Well, so far I've gotten my computer to crash five minutes before I push this [button]," says [Richard], gesturing at one of the
controls inside the [DeLorean].
</p>
</verb>
</word>
<word name="button">
<verb name="Push">
<shownev id="button.Push" main="true" /> <!-- after defaults to nevCurrent -->
<shownev id="crash" after="start" />
<p>Larry reaches for the button and gives it a push.</p>
<p>"Nothing happened", says Larry, looking at the [computer].</p>
<p>"I <i>told</i> you you were going to push it," says [Richard].</p>
<p>Larry opens his mouth to say something, then seems to think better of it.</p>
</verb>
</word>
<nev name="crash">
<p>There is a beep as [Richard Richard's] [computer] spontaneously and inexplicably reboots.</p>
<p>"Aw, geez, you're going to push the button, aren't you?", whines [Richard].</p>
<p>"What? What button?" asks Larry, puzzled.</p>
<p>"Never mind," says [Richard].</p>
</nev>
</story>
<story title="Richard and Larry Build A Time Machine" author="Jeremy Penner">
<loadmodule url="retrochronal.js" />
<nev name="start">
<p>"I still can't believe you actually used a [DeLorean]," says Larry.</p>
<p>"Hey, if you're going to do a thing, you ought to do it right," says [Richard].</p>
</nev>
<word name="DeLorean">
<verb name="Examine">
Larry looks at the [DeLorean] with astonishment. [Richard] has done it up to look <i>exactly</i> like the car from
<i>Back to the Future</i>.
</verb>
<verb name="Drive">
<p>Larry eyes the [DeLorean car]. "Give me the keys," he says. "I want to take this baby to 88."</p>
<p>"Oh, it doesn't drive anymore," says [Richard]. "I had to use the engine to power the time machine."</p>
</verb>
</word>
<word name="Richard">
<verb name="how" display="What have you done?">
<p>"So, run it by me again," says Larry.</p>
<p>"I modified this [DeLorean] to send information backwards through time," says [Richard].</p>
<p>"Just information. Not matter."</p>
<p>"Right."</p>
<p>Larry ponders this for a moment. "How... how does that even work?"</p>
<p>[Richard] waves his hands around. "Spooky action at a distance," he says.</p>
<p>"No, I mean, augh. I mean, what can you practically change about the past with this machine?" says Larry.</p>
<p>
"Well, so far I've gotten my computer to crash five minutes before I push this [button]," says [Richard],
gesturing at one of the controls inside the [DeLorean].
</p>
</verb>
</word>
<word name="button">
<verb name="Push">
<p>Larry reaches for the button and gives it a push.</p>
<p>"Nothing happened", says Larry, looking at the [computer].</p>
<p>"I <i>told</i> you you were going to push it," says [Richard].</p>
<p>Larry opens his mouth to say something, then seems to think better of it.</p>
</verb>
</word>
<pastresponse after="start" future_cause="button.Push" name="crash">
<p>There is a beep as [Richard Richard's] [computer] spontaneously and inexplicably reboots.</p>
<p>"Aw, geez, you're going to push the button, aren't you?", whines [Richard].</p>
<p>"What? What button?" asks Larry, puzzled.</p>
<p>"Never mind," says [Richard].</p>
</pastresponse>
</story>