Headless, data-driven core (no DOM, no eval): loader with one-pass validation, verb-coin interaction resolution, polygon walkables with Dijkstra pathfinding, branching dialogue with mid-conversation saves, declarative JSON conditions/effects, versioned JSON saves. Thin integration layer: asset manifest with graceful placeholders, WebAudio music loop points (midi-playlist convention), canvas renderer (hotspot boxes drawn where hitTest clicks), HUD model. Web harness (web/) + skeleton example game exercising every feature. 69 tests (node --test), tools/run_all.sh gate, docs/SPEC.md design spec. Verified end-to-end in a real browser: intro scene, walking, look/take, room exits, dialogue choices.
113 lines
4.4 KiB
JSON
113 lines
4.4 KiB
JSON
{
|
|
"meta": { "name": "skeleton-demo", "version": 1, "blurb": "Engine exercise, not a game idea: two rooms, one object you can take, one character to talk to, one branching dialogue, one intro scene." },
|
|
"startRoom": "jetty",
|
|
"startInventory": [],
|
|
"walkSpeed": 80,
|
|
"defaultVerb": "look",
|
|
"moveVerb": "walk",
|
|
|
|
"verbs": [
|
|
{ "id": "look", "label": "Look", "default": "You see nothing extraordinary." },
|
|
{ "id": "use", "label": "Use", "default": "That doesn't seem to work." },
|
|
{ "id": "talk", "label": "Talk", "default": "They're not talking." },
|
|
{ "id": "give", "label": "Give", "default": "They're not interested." },
|
|
{ "id": "walk", "label": "Walk", "default": "You can't walk there." }
|
|
],
|
|
|
|
"rooms": [
|
|
{
|
|
"id": "jetty",
|
|
"bg": "bg_jetty",
|
|
"walkable": [ [[40,300],[600,300],[600,440],[40,440]] ],
|
|
"entry": [120, 380],
|
|
"exits": [ { "to": "tavern", "at": [580, 340], "label": "Tavern" } ],
|
|
"onEnter": [ { "playMusic": "calypso" } ]
|
|
},
|
|
{
|
|
"id": "tavern",
|
|
"bg": "bg_tavern",
|
|
"walkable": [ [[60,280],[580,280],[580,440],[60,440]] ],
|
|
"entry": [120, 380],
|
|
"exits": [ { "to": "jetty", "at": [80, 340], "label": "Back to the jetty" } ]
|
|
}
|
|
],
|
|
|
|
"objects": [
|
|
{ "id": "post", "room": "jetty", "name": "a mooring post",
|
|
"hotspot": { "x": 480, "y": 300, "w": 40, "h": 90 } },
|
|
{ "id": "mug", "room": "jetty", "name": "a suspicious mug", "takes": true,
|
|
"hotspot": { "x": 200, "y": 360, "w": 40, "h": 30 }, "tags": ["container"] }
|
|
],
|
|
|
|
"characters": [
|
|
{ "id": "player", "room": "jetty", "x": 120, "y": 380, "sprite": "player" },
|
|
{ "id": "barkeep", "room": "tavern", "x": 400, "y": 320, "name": "the barkeep",
|
|
"sprite": "barkeep", "tags": ["talker"] }
|
|
],
|
|
|
|
"interactions": [
|
|
{ "verb": "look", "noun": "post", "response": "Sturdy. Smells of rope and regret." },
|
|
{ "verb": "look", "noun": "mug", "conditions": { "has": "mug" },
|
|
"response": "The mug is in your pocket. It feels important." },
|
|
{ "verb": "look", "noun": "mug", "response": "A suspicious mug. Definitely suspicious." },
|
|
{ "verb": "use", "noun": "mug", "response": "You pocket the mug. Nobody objects.",
|
|
"effects": [ { "give": "mug" }, { "setFlag": "tookMug" } ] },
|
|
{ "verb": "talk", "noun": "barkeep", "response": null,
|
|
"effects": [ { "startDialogue": "barkeep_intro" } ] },
|
|
{ "verb": "give", "with": "mug", "noun": "barkeep",
|
|
"conditions": { "not": { "flag": "gaveMug" } },
|
|
"response": "\"Where'd you get that?\" The barkeep goes very quiet.",
|
|
"effects": [ { "remove": "mug" }, { "setFlag": "gaveMug" }, { "addVar": "barkeepTrust", "value": 1 } ] },
|
|
{ "verb": "give", "with": "mug", "noun": "barkeep",
|
|
"conditions": { "flag": "gaveMug" },
|
|
"response": "You already handed it over." },
|
|
{ "verb": "look", "noun": "*", "tag": "talker", "response": "They look like they'd rather not be looked at." }
|
|
],
|
|
|
|
"dialogues": {
|
|
"barkeep_intro": {
|
|
"start": "greet",
|
|
"nodes": {
|
|
"greet": {
|
|
"speaker": "barkeep",
|
|
"text": "We're closed. Also we're open. Don't ask.",
|
|
"choices": [
|
|
{ "text": "What's in the mug?", "to": "mug" },
|
|
{ "text": "Nothing. Sorry.", "end": true }
|
|
]
|
|
},
|
|
"mug": {
|
|
"speaker": "barkeep",
|
|
"text": "What mug? There was never a mug.",
|
|
"onEnter": [ { "setFlag": "heardAboutMug" } ],
|
|
"next": "greet"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
"scenes": {
|
|
"intro": [
|
|
{ "music": "calypso" },
|
|
{ "slide": "title", "ms": 4000 },
|
|
{ "text": "A skeleton of a game, in the style of something.", "ms": 3500 },
|
|
{ "goto": "jetty" }
|
|
]
|
|
},
|
|
|
|
"music": [
|
|
{ "id": "calypso", "file": "assets/music/moonlit_mangrove_calypso.ogg",
|
|
"loopStartMs": 8460, "loopEndMs": 76920 }
|
|
],
|
|
"sfx": [
|
|
{ "id": "click", "file": "assets/sfx/click.ogg" }
|
|
],
|
|
"assets": [
|
|
{ "id": "bg_jetty", "file": "assets/img/jetty.png", "w": 640, "h": 480 },
|
|
{ "id": "bg_tavern", "file": "assets/img/tavern.png", "w": 640, "h": 480 },
|
|
{ "id": "title", "file": "assets/img/title.png", "w": 640, "h": 480 },
|
|
{ "id": "player", "file": "assets/img/player.png", "frames": { "idle": [0], "walk": [0,1,1,0] }, "framesPerRow": 4 },
|
|
{ "id": "barkeep", "file": "assets/img/barkeep.png", "frames": { "idle": [0] }, "framesPerRow": 1 }
|
|
]
|
|
}
|