@maka/maka-cli 5.122.1 → 5.123.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -64
- package/bundle/typescript/package.json +120 -120
- package/bundle/typescript/src/README.md +64 -64
- package/bundle/typescript/src/commands/aws/aws.command.js +2 -2
- package/bundle/typescript/src/commands/aws/ci.sub.cmd.js +5 -5
- package/bundle/typescript/src/commands/aws/ckp.sub.cmd.js +12 -12
- package/bundle/typescript/src/commands/aws/csg.sub.cmd.js +3 -3
- package/bundle/typescript/src/commands/aws/di.sub.cmd.js +8 -8
- package/bundle/typescript/src/commands/aws/rkp.sub.cmd.js +5 -5
- package/bundle/typescript/src/commands/aws/sth.sub.cmd.js +3 -3
- package/bundle/typescript/src/commands/aws/ti.sub.cmd.js +5 -5
- package/bundle/typescript/src/commands/build/build.js +16 -16
- package/bundle/typescript/src/commands/build/docker.sub.cmd.js +9 -9
- package/bundle/typescript/src/commands/create.js +36 -36
- package/bundle/typescript/src/commands/debug.js +4 -4
- package/bundle/typescript/src/commands/deploy/deploy.command.js +2 -2
- package/bundle/typescript/src/commands/deploy/galaxy.sub.cmd.js +2 -2
- package/bundle/typescript/src/commands/deploy/github.sub.cmd.js +8 -8
- package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js +14 -10
- package/bundle/typescript/src/commands/deploy/ubuntu.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/env/env.command.js +8 -8
- package/bundle/typescript/src/commands/env/set.sub.cmd.js +11 -11
- package/bundle/typescript/src/commands/game/sideQuest/adept-powers.d.ts +15 -0
- package/bundle/typescript/src/commands/game/sideQuest/adept-powers.js +12 -0
- package/bundle/typescript/src/commands/game/sideQuest/adept-powers.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +40 -14
- package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.d.ts +33 -10
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.js +155 -72
- package/bundle/typescript/src/commands/game/sideQuest/augmentations.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js +15 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +33 -33
- package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +6 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +4 -4
- package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.d.ts +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/initiate.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/lead.js +7 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/lead.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +3 -3
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +9 -9
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +10 -10
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/source.js +7 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/tap.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.d.ts +24 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js +95 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/chargen-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +443 -443
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +6 -6
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +45 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +120 -9
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/metatypes.d.ts +53 -0
- package/bundle/typescript/src/commands/game/sideQuest/metatypes.js +86 -0
- package/bundle/typescript/src/commands/game/sideQuest/metatypes.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +214 -208
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +215 -18
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +548 -117
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +9 -9
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +3 -2
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/qualities.d.ts +96 -4
- package/bundle/typescript/src/commands/game/sideQuest/qualities.js +290 -11
- package/bundle/typescript/src/commands/game/sideQuest/qualities.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +556 -556
- package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +1447 -1447
- package/bundle/typescript/src/commands/game/sideQuest/skill-groups.d.ts +11 -1
- package/bundle/typescript/src/commands/game/sideQuest/skill-groups.js +13 -1
- package/bundle/typescript/src/commands/game/sideQuest/skill-groups.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/skills.d.ts +17 -0
- package/bundle/typescript/src/commands/game/sideQuest/skills.js +23 -0
- package/bundle/typescript/src/commands/game/sideQuest/skills.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/spells.d.ts +76 -3
- package/bundle/typescript/src/commands/game/sideQuest/spells.js +48 -21
- package/bundle/typescript/src/commands/game/sideQuest/spells.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +8 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.d.ts +90 -82
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js +277 -10
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog-data.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.d.ts +121 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js +342 -18
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.d.ts +65 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +74 -7
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/effects.d.ts +248 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/effects.js +38 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/effects.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ephemeral.js +14 -14
- package/bundle/typescript/src/commands/game/sideQuest/utilities/first-aid.js +1 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/first-aid.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/perception.js +1 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/perception.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +5 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js +22 -12
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +1 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js +20 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/web-character-generator.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest-backlog.sub.cmd.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +33 -2
- package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
- package/bundle/typescript/src/commands/generate.js +2 -2
- package/bundle/typescript/src/commands/lint.js +10 -10
- package/bundle/typescript/src/commands/login.js +19 -1
- package/bundle/typescript/src/commands/login.js.map +1 -1
- package/bundle/typescript/src/commands/logout.js +16 -1
- package/bundle/typescript/src/commands/logout.js.map +1 -1
- package/bundle/typescript/src/commands/maka.cmd.js +26 -26
- package/bundle/typescript/src/commands/murder.js +14 -14
- package/bundle/typescript/src/commands/proxy.js +13 -13
- package/bundle/typescript/src/commands/remote/reconfig.sub.cmd.js +6 -6
- package/bundle/typescript/src/commands/run/docker.sub.cmd.js +11 -11
- package/bundle/typescript/src/commands/run/run.command.js +24 -24
- package/bundle/typescript/src/commands/set/global.sub.cmd.js +8 -8
- package/bundle/typescript/src/commands/set/local.sub.cmd.js +2 -2
- package/bundle/typescript/src/commands/set/set.command.js +3 -3
- package/bundle/typescript/src/commands/testCmd/test.command.js +2 -2
- package/bundle/typescript/src/exec.js +0 -0
- package/bundle/typescript/src/generators/api.js +11 -11
- package/bundle/typescript/src/generators/collection.js +6 -6
- package/bundle/typescript/src/generators/hook.js +10 -10
- package/bundle/typescript/src/generators/kits/auth/auth.generator.js +2 -2
- package/bundle/typescript/src/generators/kits/auth/auth0-meteor.auth.gen.js +612 -612
- package/bundle/typescript/src/generators/kits/auth/basic-meteor.auth.gen.js +48 -48
- package/bundle/typescript/src/generators/kits/auth/provider.auth.gen.js +12 -12
- package/bundle/typescript/src/generators/kits/esri/esri.generator.js +3 -3
- package/bundle/typescript/src/generators/kits/esri/map.esri.gen.js +7 -7
- package/bundle/typescript/src/generators/kits/frames/frames.generator.js +16 -16
- package/bundle/typescript/src/generators/kits/mui/auth.mui.gen.js +2 -2
- package/bundle/typescript/src/generators/kits/mui/auth0.mui.gen.js +5 -5
- package/bundle/typescript/src/generators/kits/mui/frame.mui.gen.js +4 -4
- package/bundle/typescript/src/generators/kits/mui/mui.generator.js +22 -22
- package/bundle/typescript/src/generators/services/translation.js +5 -5
- package/bundle/typescript/src/templates/aws/cloudformation/create-ec2-instance.template +14 -14
- package/bundle/typescript/src/templates/meteor/api/api.js +20 -20
- package/bundle/typescript/src/templates/meteor/api/api.js.ts +20 -20
- package/bundle/typescript/src/templates/meteor/api/apollo-gateway.js +50 -50
- package/bundle/typescript/src/templates/meteor/api/apollo-gateway.js.ts +55 -55
- package/bundle/typescript/src/templates/meteor/api/graphql-api.js +63 -63
- package/bundle/typescript/src/templates/meteor/api/graphql-api.js.ts +69 -69
- package/bundle/typescript/src/templates/meteor/api/methods.js +39 -39
- package/bundle/typescript/src/templates/meteor/api/methods.js.ts +40 -40
- package/bundle/typescript/src/templates/meteor/api/register-api.js +3 -3
- package/bundle/typescript/src/templates/meteor/api/register-api.js.ts +3 -3
- package/bundle/typescript/src/templates/meteor/api/resolvers.js +9 -9
- package/bundle/typescript/src/templates/meteor/api/resolvers.js.ts +9 -9
- package/bundle/typescript/src/templates/meteor/api/typeDefs.js +6 -6
- package/bundle/typescript/src/templates/meteor/api/typeDefs.js.ts +6 -6
- package/bundle/typescript/src/templates/meteor/app/client/body.html +3 -3
- package/bundle/typescript/src/templates/meteor/app/client/head.html +12 -12
- package/bundle/typescript/src/templates/meteor/app/client/main.js +15 -15
- package/bundle/typescript/src/templates/meteor/app/client/main.js.jsx +15 -15
- package/bundle/typescript/src/templates/meteor/app/client/main.js.ts +17 -17
- package/bundle/typescript/src/templates/meteor/app/client/main.js.tsx +17 -17
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/index.js +20 -20
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/index.js.jsx +107 -107
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/index.js.ts +20 -20
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/index.js.tsx +108 -108
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/routes.js +39 -39
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/routes.js.jsx +33 -33
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/routes.js.ts +24 -24
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/routes.js.tsx +33 -33
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/templates.js +9 -9
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/templates.js.jsx +9 -9
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/templates.js.ts +9 -9
- package/bundle/typescript/src/templates/meteor/app/imports/startup/client/templates.js.tsx +9 -9
- package/bundle/typescript/src/templates/meteor/app/imports/startup/lib/index.js +1 -1
- package/bundle/typescript/src/templates/meteor/app/imports/startup/lib/index.js.jsx +20 -20
- package/bundle/typescript/src/templates/meteor/app/imports/startup/lib/index.js.ts +1 -1
- package/bundle/typescript/src/templates/meteor/app/imports/startup/lib/index.js.tsx +20 -20
- package/bundle/typescript/src/templates/meteor/app/imports/startup/lib/routes.js.jsx +28 -28
- package/bundle/typescript/src/templates/meteor/app/imports/startup/lib/routes.js.tsx +28 -28
- package/bundle/typescript/src/templates/meteor/app/imports/startup/server/index.js +1 -1
- package/bundle/typescript/src/templates/meteor/app/imports/startup/server/index.js.jsx +95 -95
- package/bundle/typescript/src/templates/meteor/app/imports/startup/server/index.js.tsx +98 -98
- package/bundle/typescript/src/templates/meteor/app/imports/ui/test-helpers.js +24 -24
- package/bundle/typescript/src/templates/meteor/app/imports/ui/test-helpers.js.ts +24 -24
- package/bundle/typescript/src/templates/meteor/app/lib/main.js +3 -3
- package/bundle/typescript/src/templates/meteor/app/lib/main.js.jsx +3 -3
- package/bundle/typescript/src/templates/meteor/app/lib/main.js.ts +3 -3
- package/bundle/typescript/src/templates/meteor/app/lib/main.js.tsx +3 -3
- package/bundle/typescript/src/templates/meteor/app/maka.d.ts +533 -533
- package/bundle/typescript/src/templates/meteor/app/meteor.d.ts +181 -181
- package/bundle/typescript/src/templates/meteor/app/public/manifest.json +27 -27
- package/bundle/typescript/src/templates/meteor/app/public/sw.js +30 -30
- package/bundle/typescript/src/templates/meteor/app/public/sw.js.jsx +30 -30
- package/bundle/typescript/src/templates/meteor/app/public/sw.js.ts +30 -30
- package/bundle/typescript/src/templates/meteor/app/public/sw.js.tsx +30 -30
- package/bundle/typescript/src/templates/meteor/app/server/helmet-csp.js +179 -179
- package/bundle/typescript/src/templates/meteor/app/server/helmet-csp.js.jsx +179 -179
- package/bundle/typescript/src/templates/meteor/app/server/helmet-csp.js.ts +179 -179
- package/bundle/typescript/src/templates/meteor/app/server/helmet-csp.js.tsx +179 -179
- package/bundle/typescript/src/templates/meteor/app/server/main.js +13 -13
- package/bundle/typescript/src/templates/meteor/app/server/main.js.jsx +13 -13
- package/bundle/typescript/src/templates/meteor/app/server/main.js.ts +14 -14
- package/bundle/typescript/src/templates/meteor/app/server/main.js.tsx +14 -14
- package/bundle/typescript/src/templates/meteor/app/tsconfig.json +42 -42
- package/bundle/typescript/src/templates/meteor/collection/collection.js +51 -51
- package/bundle/typescript/src/templates/meteor/collection/collection.js.ts +55 -55
- package/bundle/typescript/src/templates/meteor/collection/tests.js +27 -27
- package/bundle/typescript/src/templates/meteor/collection/tests.js.ts +27 -27
- package/bundle/typescript/src/templates/meteor/config/deploy.sh.template +101 -101
- package/bundle/typescript/src/templates/meteor/config/docker-compose.yaml +17 -17
- package/bundle/typescript/src/templates/meteor/config/nginx.conf.template +31 -31
- package/bundle/typescript/src/templates/meteor/config/pm2.config.js +27 -27
- package/bundle/typescript/src/templates/meteor/config/process.env +5 -5
- package/bundle/typescript/src/templates/meteor/config/settings.json +11 -11
- package/bundle/typescript/src/templates/meteor/config/ssh.json +6 -6
- package/bundle/typescript/src/templates/meteor/dbc/config.js +70 -70
- package/bundle/typescript/src/templates/meteor/dbc/config.js.ts +70 -70
- package/bundle/typescript/src/templates/meteor/dbc/conn.js +37 -37
- package/bundle/typescript/src/templates/meteor/dbc/conn.js.ts +37 -37
- package/bundle/typescript/src/templates/meteor/dbc/register-dbc.js +3 -3
- package/bundle/typescript/src/templates/meteor/dbc/register-dbc.js.ts +3 -3
- package/bundle/typescript/src/templates/meteor/github/workflows/deploy.yaml +150 -150
- package/bundle/typescript/src/templates/meteor/method/method.js +48 -48
- package/bundle/typescript/src/templates/meteor/method/method.js.ts +48 -48
- package/bundle/typescript/src/templates/meteor/package/package.js +37 -37
- package/bundle/typescript/src/templates/meteor/package/package.js.jsx +37 -37
- package/bundle/typescript/src/templates/meteor/package/package.js.ts +36 -36
- package/bundle/typescript/src/templates/meteor/package/package.js.tsx +37 -37
- package/bundle/typescript/src/templates/meteor/package/tests/client/package.js +5 -5
- package/bundle/typescript/src/templates/meteor/package/tests/client/package.js.jsx +5 -5
- package/bundle/typescript/src/templates/meteor/package/tests/client/package.js.ts +5 -5
- package/bundle/typescript/src/templates/meteor/package/tests/client/package.js.tsx +5 -5
- package/bundle/typescript/src/templates/meteor/package/tests/server/package.js +5 -5
- package/bundle/typescript/src/templates/meteor/package/tests/server/package.js.jsx +5 -5
- package/bundle/typescript/src/templates/meteor/package/tests/server/package.js.ts +5 -5
- package/bundle/typescript/src/templates/meteor/package/tests/server/package.js.tsx +5 -5
- package/bundle/typescript/src/templates/meteor/project/.maka/config.json +29 -29
- package/bundle/typescript/src/templates/meteor/project/README.md +33 -33
- package/bundle/typescript/src/templates/meteor/project/build/README +4 -4
- package/bundle/typescript/src/templates/meteor/project/gitignore +10 -10
- package/bundle/typescript/src/templates/meteor/project/package.json +29 -29
- package/bundle/typescript/src/templates/meteor/publish/publish.js +7 -7
- package/bundle/typescript/src/templates/meteor/publish/publish.js.ts +7 -7
- package/bundle/typescript/src/templates/meteor/service/logger/logger.js +27 -27
- package/bundle/typescript/src/templates/meteor/service/logger/logger.js.ts +27 -27
- package/bundle/typescript/src/templates/meteor/stylesheet/stylesheet.css +5 -5
- package/bundle/typescript/src/templates/meteor/stylesheet/stylesheet.css.less +5 -5
- package/bundle/typescript/src/templates/meteor/template/template-tests.js +1 -1
- package/bundle/typescript/src/templates/meteor/template/template-tests.js.jsx +55 -55
- package/bundle/typescript/src/templates/meteor/template/template-tests.js.ts +1 -1
- package/bundle/typescript/src/templates/meteor/template/template-tests.js.tsx +55 -55
- package/bundle/typescript/src/templates/meteor/template/template.css +3 -3
- package/bundle/typescript/src/templates/meteor/template/template.css.less +3 -3
- package/bundle/typescript/src/templates/meteor/template/template.css.scss +3 -3
- package/bundle/typescript/src/templates/meteor/template/template.html +5 -5
- package/bundle/typescript/src/templates/meteor/template/template.js +20 -20
- package/bundle/typescript/src/templates/meteor/template/template.js.jsx +77 -77
- package/bundle/typescript/src/templates/meteor/template/template.js.ts +1 -1
- package/bundle/typescript/src/templates/meteor/template/template.js.tsx +89 -89
- package/bundle/typescript/src/templates/react/component/component.css +6 -6
- package/bundle/typescript/src/templates/react/component/component.css.less +6 -6
- package/bundle/typescript/src/templates/react/component/component.css.scss +3 -3
- package/bundle/typescript/src/templates/react/component/component.js.jsx +59 -59
- package/bundle/typescript/src/templates/react/component/component.js.tsx +65 -65
- package/bundle/typescript/src/templates/react/hook/hook.css +3 -3
- package/bundle/typescript/src/templates/react/hook/hook.css.scss +3 -3
- package/bundle/typescript/src/templates/react/hook/hook.js.jsx +19 -19
- package/bundle/typescript/src/templates/react/hook/hook.js.tsx +19 -19
- package/bundle/typescript/src/templates/react/kit/auth/provider/authentication-provider.js.jsx +306 -306
- package/bundle/typescript/src/templates/react/kit/auth/provider/authentication-provider.js.tsx +350 -350
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-client.js.jsx +178 -178
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-client.js.tsx +178 -178
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-config.js.jsx +59 -59
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-config.js.tsx +67 -67
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-methods.js.jsx +273 -273
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-methods.js.tsx +275 -275
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-validator.js.jsx +182 -182
- package/bundle/typescript/src/templates/react/kit/auth/service/auth0-validator.js.tsx +199 -199
- package/bundle/typescript/src/templates/react/kit/auth/service/meteor-auth-service.js.jsx +138 -138
- package/bundle/typescript/src/templates/react/kit/auth/service/meteor-auth-service.js.tsx +153 -153
- package/bundle/typescript/src/templates/react/kit/esri/map/basic-map.js.jsx +29 -29
- package/bundle/typescript/src/templates/react/kit/esri/map/basic-map.js.tsx +32 -32
- package/bundle/typescript/src/templates/react/kit/frames/frames-demo.css +63 -63
- package/bundle/typescript/src/templates/react/kit/frames/frames-demo.jsx +153 -153
- package/bundle/typescript/src/templates/react/kit/frames/frames-demo.tsx +164 -164
- package/bundle/typescript/src/templates/react/kit/frames/workspace-manager.css +105 -105
- package/bundle/typescript/src/templates/react/kit/frames/workspace-manager.jsx +158 -158
- package/bundle/typescript/src/templates/react/kit/frames/workspace-manager.tsx +166 -166
- package/bundle/typescript/src/templates/react/kit/mui/auth/forgot-password/basic-forgot-password-page.js.jsx +34 -34
- package/bundle/typescript/src/templates/react/kit/mui/auth/forgot-password/basic-forgot-password-page.js.tsx +33 -33
- package/bundle/typescript/src/templates/react/kit/mui/auth/login/basic-login-page.js.jsx +53 -53
- package/bundle/typescript/src/templates/react/kit/mui/auth/login/basic-login-page.js.tsx +53 -53
- package/bundle/typescript/src/templates/react/kit/mui/auth/logout/basic-logout-page.js.jsx +25 -25
- package/bundle/typescript/src/templates/react/kit/mui/auth/logout/basic-logout-page.js.tsx +25 -25
- package/bundle/typescript/src/templates/react/kit/mui/auth/password-reset/basic-password-reset-page.js.jsx +41 -41
- package/bundle/typescript/src/templates/react/kit/mui/auth/password-reset/basic-password-reset-page.js.tsx +34 -34
- package/bundle/typescript/src/templates/react/kit/mui/auth/register/basic-register-page.js.jsx +33 -33
- package/bundle/typescript/src/templates/react/kit/mui/auth/register/basic-register-page.js.tsx +33 -33
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth-callback.js.jsx +71 -71
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth-callback.js.tsx +71 -71
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-account-settings.js.jsx +123 -123
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-account-settings.js.tsx +123 -123
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-login-button.js.jsx +56 -56
- package/bundle/typescript/src/templates/react/kit/mui/auth0/auth0-login-button.js.tsx +74 -74
- package/bundle/typescript/src/templates/react/kit/mui/frame/app-bars/basic-app-bar.js.jsx +43 -43
- package/bundle/typescript/src/templates/react/kit/mui/frame/app-bars/basic-app-bar.js.tsx +43 -43
- package/bundle/typescript/src/templates/react/kit/mui/frame/drawers/basic-drawer.js.jsx +121 -121
- package/bundle/typescript/src/templates/react/kit/mui/frame/drawers/basic-drawer.js.tsx +117 -117
- package/bundle/typescript/src/templates/react/kit/mui/frame/footers/basic-sticky-footer.js.jsx +41 -41
- package/bundle/typescript/src/templates/react/kit/mui/frame/footers/basic-sticky-footer.js.tsx +41 -41
- package/bundle/typescript/src/templates/react/route/route.js +8 -8
- package/bundle/typescript/src/templates/react/route/route.js.jsx +5 -5
- package/bundle/typescript/src/templates/react/route/route.js.ts +8 -8
- package/bundle/typescript/src/templates/react/route/route.js.tsx +5 -5
- package/bundle/typescript/src/templates/react/test/test.js +1 -1
- package/bundle/typescript/src/templates/react/test/test.js.jsx +40 -40
- package/bundle/typescript/src/templates/react/test/test.js.ts +1 -1
- package/bundle/typescript/src/templates/react/test/test.js.tsx +40 -40
- package/bundle/typescript/src/templates/service/docker/Dockerfile +54 -54
- package/bundle/typescript/src/templates/service/eslint/babel.config.js +16 -16
- package/bundle/typescript/src/templates/service/eslint/config.eslint.js +69 -69
- package/bundle/typescript/src/templates/service/i18n/i18n.js +33 -33
- package/bundle/typescript/src/templates/service/i18n/i18n.js.ts +33 -33
- package/bundle/typescript/src/templates/service/i18n/locales.js +3 -3
- package/bundle/typescript/src/templates/service/i18n/locales.js.ts +3 -3
- package/bundle/typescript/src/templates/service/openssl/openssl.conf +22 -22
- package/bundle/typescript/src/templates/service/test/jest/jest.config.js +24 -24
- package/bundle/typescript/src/templates/service/test/jest/jest.setup.js +7 -7
- package/bundle/typescript/src/tools/guarded-file/guarded-file.class.d.ts +59 -0
- package/bundle/typescript/src/tools/guarded-file/guarded-file.class.js +120 -0
- package/bundle/typescript/src/tools/guarded-file/guarded-file.class.js.map +1 -0
- package/bundle/typescript/src/tools/mpi/mpi.class.d.ts +16 -0
- package/bundle/typescript/src/tools/mpi/mpi.class.js +25 -7
- package/bundle/typescript/src/tools/mpi/mpi.class.js.map +1 -1
- package/bundle/typescript/src/tools/mpi/sdk-runtime.d.ts +1 -0
- package/bundle/typescript/src/tools/mpi/sdk-runtime.js +29 -0
- package/bundle/typescript/src/tools/mpi/sdk-runtime.js.map +1 -1
- package/package.json +120 -120
|
@@ -63,8 +63,8 @@ export function isFixerName(name) {
|
|
|
63
63
|
export function wardBrief(player) {
|
|
64
64
|
return partyCanDispel(player)
|
|
65
65
|
? `- WARDS: this runner can dispel, so a "ward" device is fair game.`
|
|
66
|
-
: `- WARDS: NOBODY at this table can dispel. Do NOT write a "ward" device -- there is no
|
|
67
|
-
findable answer to a ward, so a mundane team simply cannot pass one. Use a "lock" or a
|
|
66
|
+
: `- WARDS: NOBODY at this table can dispel. Do NOT write a "ward" device -- there is no
|
|
67
|
+
findable answer to a ward, so a mundane team simply cannot pass one. Use a "lock" or a
|
|
68
68
|
"panel"; both have a way through that anyone can find.`;
|
|
69
69
|
}
|
|
70
70
|
export function buildPlayerDossier(player) {
|
|
@@ -139,34 +139,34 @@ export function buildPlayerDossier(player) {
|
|
|
139
139
|
const armor = a.getArmorValue();
|
|
140
140
|
if (armor > 0)
|
|
141
141
|
gearBits.push(`armor +${armor} soak`);
|
|
142
|
-
return `
|
|
143
|
-
THE RUNNER (build this job FOR this specific character):
|
|
144
|
-
- Name: ${a.name}${a.archetypeName ? ` -- archetype: ${a.archetypeName}` : ''}${a.gender ? ` -- ${a.gender === 'male' ? 'a man (he/him)' : 'a woman (she/her)'}` : ' -- gender unstated (use they/them, never guess)'}
|
|
145
|
-
- Attributes: ${attrs}
|
|
146
|
-
${topSkills ? `- Top skills: ${topSkills}` : ''}
|
|
147
|
-
- Combat calibration: attack pool ~${attack}d6, defense pool ~${defense}d6${gearBits.length > 0 ? ` (${gearBits.join(', ')})` : ''}
|
|
148
|
-
${caps.map(c => `- ${c}`).join('\n')}
|
|
149
|
-
|
|
150
|
-
Tailoring rules:
|
|
151
|
-
- Include at least one route, subplot, or payoff that rewards THIS runner's toolkit (matrix
|
|
152
|
-
content and hasNode rooms for a decker/technomancer; astral angles or Awakened opposition
|
|
153
|
-
for a mage; melee-shaped trouble for an adept; social leverage and people worth talking round
|
|
154
|
-
for a talker; recon value for a drone owner). Pick what fits this character and this
|
|
155
|
-
story -- don't bolt on every plane at once.
|
|
156
|
-
- Never gate FINISHING the run behind a capability they lack: no matrix-plane winCondition
|
|
157
|
-
item unless they can already jack in (deck or resonance) or a findable Cyberdeck is placed
|
|
158
|
-
in a reachable room; no ward/"spell"-bypass-only route for someone who can't cast.
|
|
159
|
-
- Don't hand them loot they can't use as a puzzle rewardItem or the winCondition rewardItem
|
|
160
|
-
(spellcasting gear for an adept, a second cyberdeck or drone for someone who already has
|
|
161
|
-
one). Useless-to-them gear may still exist as ordinary world dressing or valuables.
|
|
162
|
-
- The runner ALREADY OWNS everything the dossier lists -- never duplicate any of it (their
|
|
163
|
-
deck, drone, weapon, focus, commlink) in the "starting"-role items. Round those out with
|
|
164
|
-
complementary basics instead: ammunition, a medkit, armor, tools -- or fewer items.
|
|
165
|
-
- Scale opposition to the calibration pools above: rank-and-file threats should roll a few
|
|
166
|
-
dice below the runner's pools, and a centerpiece enforcer can match or slightly exceed
|
|
167
|
-
them. Don't send soft civilians at a combat monster, or elite security at a runner built
|
|
168
|
-
for talking their way through.
|
|
169
|
-
${wardBrief(player)}
|
|
142
|
+
return `
|
|
143
|
+
THE RUNNER (build this job FOR this specific character):
|
|
144
|
+
- Name: ${a.name}${a.archetypeName ? ` -- archetype: ${a.archetypeName}` : ''}${a.gender ? ` -- ${a.gender === 'male' ? 'a man (he/him)' : 'a woman (she/her)'}` : ' -- gender unstated (use they/them, never guess)'}
|
|
145
|
+
- Attributes: ${attrs}
|
|
146
|
+
${topSkills ? `- Top skills: ${topSkills}` : ''}
|
|
147
|
+
- Combat calibration: attack pool ~${attack}d6, defense pool ~${defense}d6${gearBits.length > 0 ? ` (${gearBits.join(', ')})` : ''}
|
|
148
|
+
${caps.map(c => `- ${c}`).join('\n')}
|
|
149
|
+
|
|
150
|
+
Tailoring rules:
|
|
151
|
+
- Include at least one route, subplot, or payoff that rewards THIS runner's toolkit (matrix
|
|
152
|
+
content and hasNode rooms for a decker/technomancer; astral angles or Awakened opposition
|
|
153
|
+
for a mage; melee-shaped trouble for an adept; social leverage and people worth talking round
|
|
154
|
+
for a talker; recon value for a drone owner). Pick what fits this character and this
|
|
155
|
+
story -- don't bolt on every plane at once.
|
|
156
|
+
- Never gate FINISHING the run behind a capability they lack: no matrix-plane winCondition
|
|
157
|
+
item unless they can already jack in (deck or resonance) or a findable Cyberdeck is placed
|
|
158
|
+
in a reachable room; no ward/"spell"-bypass-only route for someone who can't cast.
|
|
159
|
+
- Don't hand them loot they can't use as a puzzle rewardItem or the winCondition rewardItem
|
|
160
|
+
(spellcasting gear for an adept, a second cyberdeck or drone for someone who already has
|
|
161
|
+
one). Useless-to-them gear may still exist as ordinary world dressing or valuables.
|
|
162
|
+
- The runner ALREADY OWNS everything the dossier lists -- never duplicate any of it (their
|
|
163
|
+
deck, drone, weapon, focus, commlink) in the "starting"-role items. Round those out with
|
|
164
|
+
complementary basics instead: ammunition, a medkit, armor, tools -- or fewer items.
|
|
165
|
+
- Scale opposition to the calibration pools above: rank-and-file threats should roll a few
|
|
166
|
+
dice below the runner's pools, and a centerpiece enforcer can match or slightly exceed
|
|
167
|
+
them. Don't send soft civilians at a combat monster, or elite security at a runner built
|
|
168
|
+
for talking their way through.
|
|
169
|
+
${wardBrief(player)}
|
|
170
170
|
`.trim();
|
|
171
171
|
}
|
|
172
172
|
// ============================ Prompt builders ============================
|
|
@@ -200,11 +200,11 @@ function buildTeamBlock(crew) {
|
|
|
200
200
|
.map(([s, r]) => `${s} ${r}`).join(', ');
|
|
201
201
|
return `- ${m.name} (${m.archetypeName ?? 'street contact'}): body ${c.body ?? 3}, agility ${c.agility ?? 3}, logic ${c.logic ?? 3}${(c.magic ?? 0) > 0 ? `, magic ${c.magic}` : ''}${topSkills ? ` -- ${topSkills}` : ''}`;
|
|
202
202
|
}).join('\n');
|
|
203
|
-
return `
|
|
204
|
-
THE TEAM: the runner does NOT work alone -- their crew rides along on this job:
|
|
205
|
-
${rows}
|
|
206
|
-
The opposition must credibly threaten a team of ${crew.length + 1} runners: scale guard
|
|
207
|
-
counts and quality up accordingly (one extra capable defender per crew member is a good
|
|
203
|
+
return `
|
|
204
|
+
THE TEAM: the runner does NOT work alone -- their crew rides along on this job:
|
|
205
|
+
${rows}
|
|
206
|
+
The opposition must credibly threaten a team of ${crew.length + 1} runners: scale guard
|
|
207
|
+
counts and quality up accordingly (one extra capable defender per crew member is a good
|
|
208
208
|
baseline), and let the fiction acknowledge a crew, not a loner.`;
|
|
209
209
|
}
|
|
210
210
|
export function buildSkeletonPrompt(tier, player, previousError, rumor, crew, canonBlock, runType = 'standard') {
|
|
@@ -254,409 +254,409 @@ export function buildSkeletonPrompt(tier, player, previousError, rumor, crew, ca
|
|
|
254
254
|
// fixer" possible at all and why the objective below can be a `give`.
|
|
255
255
|
// The two halves have to agree about WHERE the file is, so this block
|
|
256
256
|
// says host-or-offline-server in the same vocabulary room.ts uses.
|
|
257
|
-
const dataRunBlock = runType !== 'data' ? '' : `
|
|
258
|
-
THIS JOB IS A DATA RUN -- a quiet one. The runner is being paid to STEAL A FILE, not to
|
|
259
|
-
take ground. Build it accordingly:
|
|
260
|
-
- The winCondition item is PAYDATA: a "matrix"-plane item, category Document, sitting
|
|
261
|
-
either INSIDE a hasNode host, or on an OFFLINE SERVER device in a room (a box with no
|
|
262
|
-
Matrix presence, whose files open only to a runner whose BODY is in that room). Pick one
|
|
263
|
-
and commit to it -- the offline server is the harder, more interesting shape when the
|
|
264
|
-
fiction wants a vault you must walk into.
|
|
265
|
-
- THE OPPOSITION IS ICE, not muscle. Every NPC besides the client is "matrix"-plane and
|
|
266
|
-
stationed in the host. No guards patrolling meat corridors, no gunfight in the lobby: a
|
|
267
|
-
data run that ends in a firefight is a smash-and-grab wearing the wrong name. A site can
|
|
268
|
-
still be WATCHED (cameras) -- surveillance is pressure without being a gun.
|
|
269
|
-
- Keep it SHORT and tight. One site, one host, one file.
|
|
270
|
-
- It pays LESS than a full run of the same tier -- roughly half. The runner is trading pay
|
|
271
|
-
for a job that never puts a gun in their face, and the fixer prices it that way. Say so
|
|
272
|
-
in the client's own terms if it comes up at the meet.
|
|
273
|
-
- The delivery still matters: the file comes out of the vault written to a chip, and the
|
|
274
|
-
chip is a physical object the runner carries and hands over. A "give" objective is the
|
|
275
|
-
natural shape.
|
|
257
|
+
const dataRunBlock = runType !== 'data' ? '' : `
|
|
258
|
+
THIS JOB IS A DATA RUN -- a quiet one. The runner is being paid to STEAL A FILE, not to
|
|
259
|
+
take ground. Build it accordingly:
|
|
260
|
+
- The winCondition item is PAYDATA: a "matrix"-plane item, category Document, sitting
|
|
261
|
+
either INSIDE a hasNode host, or on an OFFLINE SERVER device in a room (a box with no
|
|
262
|
+
Matrix presence, whose files open only to a runner whose BODY is in that room). Pick one
|
|
263
|
+
and commit to it -- the offline server is the harder, more interesting shape when the
|
|
264
|
+
fiction wants a vault you must walk into.
|
|
265
|
+
- THE OPPOSITION IS ICE, not muscle. Every NPC besides the client is "matrix"-plane and
|
|
266
|
+
stationed in the host. No guards patrolling meat corridors, no gunfight in the lobby: a
|
|
267
|
+
data run that ends in a firefight is a smash-and-grab wearing the wrong name. A site can
|
|
268
|
+
still be WATCHED (cameras) -- surveillance is pressure without being a gun.
|
|
269
|
+
- Keep it SHORT and tight. One site, one host, one file.
|
|
270
|
+
- It pays LESS than a full run of the same tier -- roughly half. The runner is trading pay
|
|
271
|
+
for a job that never puts a gun in their face, and the fixer prices it that way. Say so
|
|
272
|
+
in the client's own terms if it comes up at the meet.
|
|
273
|
+
- The delivery still matters: the file comes out of the vault written to a chip, and the
|
|
274
|
+
chip is a physical object the runner carries and hands over. A "give" objective is the
|
|
275
|
+
natural shape.
|
|
276
276
|
`;
|
|
277
277
|
// The hub's active street rumor becomes the job's backdrop -- gossip
|
|
278
278
|
// the runner heard at the noodle stand pays off as foreshadowing when
|
|
279
279
|
// the next job turns out to be tangled in exactly that (see
|
|
280
280
|
// Game.advanceRumor).
|
|
281
281
|
const rumorBlock = rumor
|
|
282
|
-
? `\nSTREET RUMOR the runner has been hearing around their home district: "${rumor}"
|
|
283
|
-
Let this job CONNECT to the rumor -- as its cause, its consequence, or the truth behind it.
|
|
284
|
-
The connection should be discoverable in play (the client's briefing, an NPC's talk, a
|
|
282
|
+
? `\nSTREET RUMOR the runner has been hearing around their home district: "${rumor}"
|
|
283
|
+
Let this job CONNECT to the rumor -- as its cause, its consequence, or the truth behind it.
|
|
284
|
+
The connection should be discoverable in play (the client's briefing, an NPC's talk, a
|
|
285
285
|
document), not just implied by the title.`
|
|
286
286
|
: '';
|
|
287
|
-
return `
|
|
288
|
-
You are generating the STRUCTURE (skeleton) of a self-contained Shadowrun-flavored text
|
|
289
|
-
adventure "run". This is job #${tier} in an ongoing campaign -- scale stakes accordingly.
|
|
290
|
-
Prose comes later in separate passes: here, every "concept" is ONE tight sentence of intent,
|
|
291
|
-
and everything else is structure. Get the STRUCTURE exactly right -- every name is a
|
|
292
|
-
cross-reference other passes and the game engine will rely on verbatim.
|
|
293
|
-
${rumorBlock}
|
|
294
|
-
${dataRunBlock}
|
|
295
|
-
${canonBlock ? `\n${canonBlock}\n` : ''}
|
|
296
|
-
${dossier ? `\n${dossier}\n` : ''}
|
|
297
|
-
${JSON_ONLY}
|
|
298
|
-
|
|
299
|
-
{
|
|
300
|
-
"name": string, // the run's title
|
|
301
|
-
"story": string, // 1-2 SHORT teaser sentences shown before play -- name the shape of the
|
|
302
|
-
// trouble, never the objective, target names, locations, or passphrases
|
|
303
|
-
// (those are learned at the meet with the client).
|
|
304
|
-
"completionMessage": string, // 1-2 sentences shown when the job completes
|
|
305
|
-
"rooms": [{
|
|
306
|
-
"name": string, "concept": string, "roomType"?: string,
|
|
307
|
-
"outdoors": boolean, // TRUE only if OPEN TO THE SKY. A courtyard, a street, a
|
|
308
|
-
// rooftop, a dock: true. Anything with a roof -- a market
|
|
309
|
-
// HALL, a parking garage, a docking bay, a courtROOM -- is
|
|
310
|
-
// false, however outdoor its name sounds. Say it for every
|
|
311
|
-
// room; the engine cannot tell from the name and a wrong
|
|
312
|
-
// guess sends a taxi inside a building.
|
|
313
|
-
"isStartRoom"?: boolean, // EXACTLY ONE room
|
|
314
|
-
"reachable"?: boolean, // false ONLY for a remote contact's room (rare -- a voice on the
|
|
315
|
-
// other end of a call, never anyone the player must meet)
|
|
316
|
-
"owner"?: string, // an NPC's name, if it's their room. USE THIS for any territorial or
|
|
317
|
-
// guarded space (a gang cache, a fixer's office, a shop): the room's
|
|
318
|
-
// goods become THEIRS, and the engine makes them contest theft --
|
|
319
|
-
// an unowned prize in a guarded vault walks out unchallenged.
|
|
320
|
-
"hasNode"?: boolean, // a live Matrix host standing over this room: its own virtual space,
|
|
321
|
-
// ENTERED by a persona that holds a mark on it. Hacking it releases
|
|
322
|
-
// the room's maglocks AND unseals its matrix-plane files, which live
|
|
323
|
-
// INSIDE the host -- from the outside grid it is one sealed icon.
|
|
324
|
-
// A HOST IS ORDINARY, NOT CORPORATE. "Your local Stuffer Shack has
|
|
325
|
-
// a host icon" (p.219), and the core book publishes that convenience
|
|
326
|
-
// store as a worked sample host (p.245-246) -- so a pawnshop, a bar,
|
|
327
|
-
// a clinic or a corner store plausibly has one, rated 3-4 as "low-end
|
|
328
|
-
// commercial, private business" (p.247). Withholding hosts from
|
|
329
|
-
// everything but corp/tech sites is what made an ordinary storefront
|
|
330
|
-
// read "thin grid" on the map when canon says otherwise.
|
|
331
|
-
// SET IT ON PREMISES, NOT ON EVERY ROOM: the businesses and secure
|
|
332
|
-
// sites a scene contains, not each corridor, alley, stairwell or
|
|
333
|
-
// room inside one building -- a site is covered by its host, and
|
|
334
|
-
// interior rooms of the same premises share it rather than each
|
|
335
|
-
// sprouting their own. Give every host a hostRating chosen for the
|
|
336
|
-
// place it actually is; a scene where every rating is 3 is as wrong
|
|
337
|
-
// as one where nothing is a host.
|
|
338
|
-
// Ice NPCs and matrix-plane items belong ONLY in hasNode rooms, and
|
|
339
|
-
// any hasNode room holding matrix items MUST have an ice NPC
|
|
340
|
-
// stationed in it -- so a corner-store host with no paydata in it
|
|
341
|
-
// carries no ice, and costs the scene nothing.
|
|
342
|
-
"nodeAccess"?: "wireless", // Hosts are always reachable from the Matrix -- that is what
|
|
343
|
-
// makes them hosts. There is no air-gapped host: for a vault that
|
|
344
|
-
// must be entered in the flesh, put an OFFLINE SERVER device in the
|
|
345
|
-
// room's items instead and leave the host off. Legacy "wired" values
|
|
346
|
-
// are migrated automatically; do not author them.
|
|
347
|
-
"hostGrid"?: "public" | "local" | "global:<corp>", // OPTIONAL. Which grid the host sits on (SR5 p.233 cross-grid -2). Leave it out unless the fiction says -- hosts are reachable from every grid.
|
|
348
|
-
"hostRating"?: number, // 1-12, SR5 p.247. What KIND of host this is, and the only thing
|
|
349
|
-
// that tells a pawnshop apart from an arcology on the grid map.
|
|
350
|
-
// 1-2 personal sites, pirate archives. 3-4 low-end commercial,
|
|
351
|
-
// private business. 5-6 social media, local police. 7-8 local
|
|
352
|
-
// corporate, low-level government. 9-10 regional corporate, secure
|
|
353
|
-
// sites. 11-12 megacorporate HQ, military command. Rate it for the
|
|
354
|
-
// place it actually is; omit it and the host is treated as a 3.
|
|
355
|
-
"warded"?: boolean, // astral barrier: projecting mages can't enter. Use sparingly.
|
|
356
|
-
"watched"?: boolean, // security cameras cover this room (feeds ride the site's hosts;
|
|
357
|
-
// hasNode rooms are always watched implicitly). Use for lobbies,
|
|
358
|
-
// corridors, and secure floors in corp/guarded locations -- loud
|
|
359
|
-
// acts on camera cost runners dearly, and deckers loop the feeds.
|
|
360
|
-
"exits": [{ "direction": "north"|"east"|"south"|"west"|"up"|"down", "targetRoom": string,
|
|
361
|
-
"keyRequired"?: string, // an items[].name with role "key"
|
|
362
|
-
"device"?: string, // a devices[].name that holds this door shut. That
|
|
363
|
-
// device's own "room" MUST be the room declaring this
|
|
364
|
-
// exit -- you have to stand where the mechanism is.
|
|
365
|
-
"guard"?: string }] // an npcs[].name who STANDS in this doorway and will
|
|
366
|
-
// not move. A person, not a mechanism: the way past
|
|
367
|
-
// is talking them round or putting them down.
|
|
368
|
-
// NEVER set "device" and "guard" on one exit -- a
|
|
369
|
-
// door is gated by a thing OR a person, never both.
|
|
370
|
-
// Doors are TWO-WAY automatically: declare each connection from ONE
|
|
371
|
-
// side only (with its keyRequired/puzzle on that one declaration) --
|
|
372
|
-
// never declare the reverse direction too.
|
|
373
|
-
}],
|
|
374
|
-
// STAFF YOUR COMMERCE. Any room that sells, serves, or treats -- a
|
|
375
|
-
// market, shop, bar, restaurant, clinic, pawnshop -- MUST have an
|
|
376
|
-
// npcs[] entry stationed in it. A shop with nobody behind the counter
|
|
377
|
-
// is a room the runner walks into and finds blank, no matter how good
|
|
378
|
-
// its description reads.
|
|
379
|
-
"npcs": [{
|
|
380
|
-
"name": string, "concept": string, "startLocation": string,
|
|
381
|
-
"plane"?: "meat"|"matrix"|"astral", // "matrix" = ICE guarding a hasNode room;
|
|
382
|
-
// "astral" = a spirit. Default meat.
|
|
383
|
-
"hostile"?: boolean // OPPOSITION: this one KNOWS the runner does not belong and will
|
|
384
|
-
// act on it -- a posted guard, a patrolling enforcer, a gang
|
|
385
|
-
// holding the room. Default false, and LEAVE IT FALSE unless the
|
|
386
|
-
// concept is genuinely someone's security. NOT for staff who
|
|
387
|
-
// merely work for the wrong people: a bartender, a clerk, a
|
|
388
|
-
// ripperdoc and a fixer are all NEUTRAL even inside an enemy
|
|
389
|
-
// building, and marking them turns an ordinary scene into a
|
|
390
|
-
// brawl. If in doubt, leave it out -- a guard who stays calm is
|
|
391
|
-
// a missed beat, a hostile bartender is a broken scene.
|
|
392
|
-
// ICE NEEDS NO FLAG: a "matrix" NPC is intrusion countermeasures
|
|
393
|
-
// and is treated as opposition automatically. Write it only to
|
|
394
|
-
// say FALSE, for the rare tame construct.
|
|
395
|
-
}],
|
|
396
|
-
"devices": [{
|
|
397
|
-
"name": string, "concept": string, // what it physically IS (a maglock, a keypad, a
|
|
398
|
-
// scanner, a ward) -- a THING, never a person
|
|
399
|
-
"room": string, // REQUIRED. A device always stands in ONE room -- the one you must be
|
|
400
|
-
// in to reach it. If it holds a door, declare that exit FROM THIS
|
|
401
|
-
// SAME ROOM with "device": "<this name>".
|
|
402
|
-
"kind": "lock"|"panel"|"ward", // lock = a mechanism: picked, breached, or opened by its
|
|
403
|
-
// key. panel = an interface: a code, or hacked. ward = a working:
|
|
404
|
-
// cast at or dispelled. EXACTLY these lowercase strings.
|
|
405
|
-
// A device is always a THING. A doorman or a checkpoint guard is
|
|
406
|
-
// NOT a device -- write them into npcs[] and name them in that
|
|
407
|
-
// exit's "guard"; people are got past socially, not picked.
|
|
408
|
-
"grantsItem"?: string // an items[].name with role "reward" -- what opening it hands
|
|
409
|
-
// over. Omit for a device that holds a door: the open way IS
|
|
410
|
-
// the reward.
|
|
411
|
-
}],
|
|
412
|
-
"items": [{
|
|
413
|
-
"name": string,
|
|
414
|
-
"role": "winCondition"|"reward"|"key"|"clue"|"loot"|"starting",
|
|
415
|
-
"category": string, // one of: Weapon, MeleeWeapon, RangedWeapon, Armor, BodyArmor,
|
|
416
|
-
// ArmorClothing, Helmet, Hat, LegArmor, ArmArmor, Gloves, Commlink,
|
|
417
|
-
// Cyberdeck, Drone, Credstick, CurrencyContainer, Key, Trinket,
|
|
418
|
-
// Document, Datachip, Vice, Book, Tool, Clothing, Glasses, Boots,
|
|
419
|
-
// Medical, StimPatch, SpellFormula, Grimoire, Focus, Ammunition,
|
|
420
|
-
// Food, Drink (consumables -- hot food heals stun, drink is liquid
|
|
421
|
-
// courage; fine as dressing in bars/kitchens, never as prizes),
|
|
422
|
-
// Cyberware (RARE prize loot -- an uninstalled augmentation part.
|
|
423
|
-
// Its name must be one of: ${AUGMENTATIONS.map(a => a.name).join(', ')};
|
|
424
|
-
// never one the runner already has installed, never role "starting")
|
|
425
|
-
"room"?: string, "heldBy"?: string, // exactly one of these for roles winCondition/key/
|
|
426
|
-
// clue/loot; NEITHER for roles reward/starting
|
|
427
|
-
"plane"?: "meat"|"matrix", // "matrix" = paydata: only in a hasNode room, category
|
|
428
|
-
// Datachip/Document. A matrix-plane winCondition item makes
|
|
429
|
-
// the job a datarun (its room MUST have ice).
|
|
430
|
-
"forPuzzle"?: string // role "clue" only: which puzzle's passphrase this clue reveals.
|
|
431
|
-
// EVERY puzzle needs exactly one clue item, category "Book",
|
|
432
|
-
// placed in a DIFFERENT room than that puzzle (door puzzles: any
|
|
433
|
-
// room). The clue's text is written in a later pass.
|
|
434
|
-
// ${isContinuation ? 'Role "starting" is FORBIDDEN in this scene -- the runner arrives fully equipped (see dossier).' : 'Role "starting" items are the fresh runner\'s basic kit.'}
|
|
435
|
-
}],
|
|
436
|
-
"winCondition": {
|
|
437
|
-
"item": string, // the items[].name with role "winCondition" -- MUST have room or heldBy
|
|
438
|
-
// set (never heldBy "toNpc" -- the player has to find it), and it is
|
|
439
|
-
// the central plot object of "story"
|
|
440
|
-
"toNpc": string, // an npcs[].name -- who wants it delivered
|
|
441
|
-
"payout": number, // the job's pay in nuyen; the engine actually transfers it -- half at
|
|
442
|
-
// the meet, the rest on delivery. ~2000-6000 for job #1, roughly half
|
|
443
|
-
// again more per job after that
|
|
444
|
-
"rewardItem"?: string // optional bonus: an items[].name with role "reward"
|
|
445
|
-
},
|
|
446
|
-
"client": string // the npcs[].name of the run's CLIENT -- see CLIENT below
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
CLIENT: every run has a CLIENT -- "a Mr. Johnson", the employer's deniable face, the person
|
|
450
|
-
who holds THE MEET (briefs the runner in person, negotiates the rate, pays the whole fee as
|
|
451
|
-
one lump sum on delivery). Invent a COVER NAME with corporate anonymity to it -- "Mr. Osprey",
|
|
452
|
-
"Ms. Veil", "Señora Marrow" -- never literally "Mr. Johnson" and never "${FIXER_NAME}" (that's
|
|
453
|
-
the runner's own fixer back home, who brokered this meet and is NOT in this scene). Station
|
|
454
|
-
the client in a REACHABLE room that makes a characterful meet spot -- a bar booth, a noodle
|
|
455
|
-
stall, the back seat of a parked limo, a chapel pew -- reachable from the start room WITHOUT
|
|
456
|
-
passing any puzzle, key, or hostile ground: the meet opens the job, it is never gated behind
|
|
457
|
-
it. The client MUST hold a Commlink-category item (role "loot", heldBy them) so the runner
|
|
458
|
-
can also call them, and the player must have a commlink available (${isContinuation ? 'the runner already carries their own -- see the dossier' : '"starting" role, or findable in a reachable room'}).
|
|
459
|
-
The client usually has no combat block -- they hired OUT the violence. They do NOT have to
|
|
460
|
-
be winCondition.toNpc (delivering to the client at the meet spot is one good shape; a
|
|
461
|
-
dead-drop to a different contact is another); any toNpc in an unreachable room needs a
|
|
462
|
-
commlink of their own too.
|
|
463
|
-
|
|
464
|
-
Hard structural rules (validated mechanically -- a violation is rejected):
|
|
465
|
-
- Every exits[].targetRoom, npcs[].startLocation, items[].room name a real rooms[].name;
|
|
466
|
-
every items[].heldBy names a real npcs[].name; every exits[].device names a real
|
|
467
|
-
puzzles[].name; every keyRequired names a real items[].name with role "key".
|
|
468
|
-
- "client" names a real npcs[].name, stationed in a REACHABLE room, holding a
|
|
469
|
-
Commlink-category item.
|
|
470
|
-
- Exactly one room has isStartRoom true, and it must be OUTDOOR street-level ground -- a
|
|
471
|
-
street, alley, plaza, lot, dock, yard -- with "outdoors": true AND a name/roomType that
|
|
472
|
-
says so. The runner
|
|
473
|
-
arrives by cab at that curb and hails the ride home from the same spot; an indoor start
|
|
474
|
-
room breaks the transport fiction. Never lock the start room's only exits behind keys
|
|
475
|
-
placed beyond them: every key must be reachable WITHOUT passing through the door it opens.
|
|
476
|
-
- A "reachable": false room never connects to a reachable one, and nothing the player must
|
|
477
|
-
retrieve (winCondition item, keys, clues) is ever placed in one.
|
|
478
|
-
- EVERY device names its own "room". If it holds a door, that door's exit is declared
|
|
479
|
-
from THAT SAME room with "device": "<its name>".
|
|
480
|
-
- EVERY WAY THROUGH IS FINDABLE -- no exceptions, and no dependence on who is playing:
|
|
481
|
-
* every "panel" gets a code AND exactly one "clue"-role Book item (forDevice set) in a
|
|
482
|
-
DIFFERENT room, reachable without passing the panel itself;
|
|
483
|
-
* every "lock" gets a "key"-role item placed in the world, reachable without passing
|
|
484
|
-
the lock itself.
|
|
485
|
-
Write the clue or the key in the same breath as the device. A runner with the matching
|
|
486
|
-
skill can still take the shortcut -- pick the lock, hack the panel -- but the found way
|
|
487
|
-
must exist for a runner who has neither.
|
|
488
|
-
- The winCondition item must not be heldBy winCondition.toNpc. If a matrix-plane item is the
|
|
489
|
-
winCondition item, its room has hasNode true AND a "matrix"-plane NPC stationed there.
|
|
287
|
+
return `
|
|
288
|
+
You are generating the STRUCTURE (skeleton) of a self-contained Shadowrun-flavored text
|
|
289
|
+
adventure "run". This is job #${tier} in an ongoing campaign -- scale stakes accordingly.
|
|
290
|
+
Prose comes later in separate passes: here, every "concept" is ONE tight sentence of intent,
|
|
291
|
+
and everything else is structure. Get the STRUCTURE exactly right -- every name is a
|
|
292
|
+
cross-reference other passes and the game engine will rely on verbatim.
|
|
293
|
+
${rumorBlock}
|
|
294
|
+
${dataRunBlock}
|
|
295
|
+
${canonBlock ? `\n${canonBlock}\n` : ''}
|
|
296
|
+
${dossier ? `\n${dossier}\n` : ''}
|
|
297
|
+
${JSON_ONLY}
|
|
298
|
+
|
|
299
|
+
{
|
|
300
|
+
"name": string, // the run's title
|
|
301
|
+
"story": string, // 1-2 SHORT teaser sentences shown before play -- name the shape of the
|
|
302
|
+
// trouble, never the objective, target names, locations, or passphrases
|
|
303
|
+
// (those are learned at the meet with the client).
|
|
304
|
+
"completionMessage": string, // 1-2 sentences shown when the job completes
|
|
305
|
+
"rooms": [{
|
|
306
|
+
"name": string, "concept": string, "roomType"?: string,
|
|
307
|
+
"outdoors": boolean, // TRUE only if OPEN TO THE SKY. A courtyard, a street, a
|
|
308
|
+
// rooftop, a dock: true. Anything with a roof -- a market
|
|
309
|
+
// HALL, a parking garage, a docking bay, a courtROOM -- is
|
|
310
|
+
// false, however outdoor its name sounds. Say it for every
|
|
311
|
+
// room; the engine cannot tell from the name and a wrong
|
|
312
|
+
// guess sends a taxi inside a building.
|
|
313
|
+
"isStartRoom"?: boolean, // EXACTLY ONE room
|
|
314
|
+
"reachable"?: boolean, // false ONLY for a remote contact's room (rare -- a voice on the
|
|
315
|
+
// other end of a call, never anyone the player must meet)
|
|
316
|
+
"owner"?: string, // an NPC's name, if it's their room. USE THIS for any territorial or
|
|
317
|
+
// guarded space (a gang cache, a fixer's office, a shop): the room's
|
|
318
|
+
// goods become THEIRS, and the engine makes them contest theft --
|
|
319
|
+
// an unowned prize in a guarded vault walks out unchallenged.
|
|
320
|
+
"hasNode"?: boolean, // a live Matrix host standing over this room: its own virtual space,
|
|
321
|
+
// ENTERED by a persona that holds a mark on it. Hacking it releases
|
|
322
|
+
// the room's maglocks AND unseals its matrix-plane files, which live
|
|
323
|
+
// INSIDE the host -- from the outside grid it is one sealed icon.
|
|
324
|
+
// A HOST IS ORDINARY, NOT CORPORATE. "Your local Stuffer Shack has
|
|
325
|
+
// a host icon" (p.219), and the core book publishes that convenience
|
|
326
|
+
// store as a worked sample host (p.245-246) -- so a pawnshop, a bar,
|
|
327
|
+
// a clinic or a corner store plausibly has one, rated 3-4 as "low-end
|
|
328
|
+
// commercial, private business" (p.247). Withholding hosts from
|
|
329
|
+
// everything but corp/tech sites is what made an ordinary storefront
|
|
330
|
+
// read "thin grid" on the map when canon says otherwise.
|
|
331
|
+
// SET IT ON PREMISES, NOT ON EVERY ROOM: the businesses and secure
|
|
332
|
+
// sites a scene contains, not each corridor, alley, stairwell or
|
|
333
|
+
// room inside one building -- a site is covered by its host, and
|
|
334
|
+
// interior rooms of the same premises share it rather than each
|
|
335
|
+
// sprouting their own. Give every host a hostRating chosen for the
|
|
336
|
+
// place it actually is; a scene where every rating is 3 is as wrong
|
|
337
|
+
// as one where nothing is a host.
|
|
338
|
+
// Ice NPCs and matrix-plane items belong ONLY in hasNode rooms, and
|
|
339
|
+
// any hasNode room holding matrix items MUST have an ice NPC
|
|
340
|
+
// stationed in it -- so a corner-store host with no paydata in it
|
|
341
|
+
// carries no ice, and costs the scene nothing.
|
|
342
|
+
"nodeAccess"?: "wireless", // Hosts are always reachable from the Matrix -- that is what
|
|
343
|
+
// makes them hosts. There is no air-gapped host: for a vault that
|
|
344
|
+
// must be entered in the flesh, put an OFFLINE SERVER device in the
|
|
345
|
+
// room's items instead and leave the host off. Legacy "wired" values
|
|
346
|
+
// are migrated automatically; do not author them.
|
|
347
|
+
"hostGrid"?: "public" | "local" | "global:<corp>", // OPTIONAL. Which grid the host sits on (SR5 p.233 cross-grid -2). Leave it out unless the fiction says -- hosts are reachable from every grid.
|
|
348
|
+
"hostRating"?: number, // 1-12, SR5 p.247. What KIND of host this is, and the only thing
|
|
349
|
+
// that tells a pawnshop apart from an arcology on the grid map.
|
|
350
|
+
// 1-2 personal sites, pirate archives. 3-4 low-end commercial,
|
|
351
|
+
// private business. 5-6 social media, local police. 7-8 local
|
|
352
|
+
// corporate, low-level government. 9-10 regional corporate, secure
|
|
353
|
+
// sites. 11-12 megacorporate HQ, military command. Rate it for the
|
|
354
|
+
// place it actually is; omit it and the host is treated as a 3.
|
|
355
|
+
"warded"?: boolean, // astral barrier: projecting mages can't enter. Use sparingly.
|
|
356
|
+
"watched"?: boolean, // security cameras cover this room (feeds ride the site's hosts;
|
|
357
|
+
// hasNode rooms are always watched implicitly). Use for lobbies,
|
|
358
|
+
// corridors, and secure floors in corp/guarded locations -- loud
|
|
359
|
+
// acts on camera cost runners dearly, and deckers loop the feeds.
|
|
360
|
+
"exits": [{ "direction": "north"|"east"|"south"|"west"|"up"|"down", "targetRoom": string,
|
|
361
|
+
"keyRequired"?: string, // an items[].name with role "key"
|
|
362
|
+
"device"?: string, // a devices[].name that holds this door shut. That
|
|
363
|
+
// device's own "room" MUST be the room declaring this
|
|
364
|
+
// exit -- you have to stand where the mechanism is.
|
|
365
|
+
"guard"?: string }] // an npcs[].name who STANDS in this doorway and will
|
|
366
|
+
// not move. A person, not a mechanism: the way past
|
|
367
|
+
// is talking them round or putting them down.
|
|
368
|
+
// NEVER set "device" and "guard" on one exit -- a
|
|
369
|
+
// door is gated by a thing OR a person, never both.
|
|
370
|
+
// Doors are TWO-WAY automatically: declare each connection from ONE
|
|
371
|
+
// side only (with its keyRequired/puzzle on that one declaration) --
|
|
372
|
+
// never declare the reverse direction too.
|
|
373
|
+
}],
|
|
374
|
+
// STAFF YOUR COMMERCE. Any room that sells, serves, or treats -- a
|
|
375
|
+
// market, shop, bar, restaurant, clinic, pawnshop -- MUST have an
|
|
376
|
+
// npcs[] entry stationed in it. A shop with nobody behind the counter
|
|
377
|
+
// is a room the runner walks into and finds blank, no matter how good
|
|
378
|
+
// its description reads.
|
|
379
|
+
"npcs": [{
|
|
380
|
+
"name": string, "concept": string, "startLocation": string,
|
|
381
|
+
"plane"?: "meat"|"matrix"|"astral", // "matrix" = ICE guarding a hasNode room;
|
|
382
|
+
// "astral" = a spirit. Default meat.
|
|
383
|
+
"hostile"?: boolean // OPPOSITION: this one KNOWS the runner does not belong and will
|
|
384
|
+
// act on it -- a posted guard, a patrolling enforcer, a gang
|
|
385
|
+
// holding the room. Default false, and LEAVE IT FALSE unless the
|
|
386
|
+
// concept is genuinely someone's security. NOT for staff who
|
|
387
|
+
// merely work for the wrong people: a bartender, a clerk, a
|
|
388
|
+
// ripperdoc and a fixer are all NEUTRAL even inside an enemy
|
|
389
|
+
// building, and marking them turns an ordinary scene into a
|
|
390
|
+
// brawl. If in doubt, leave it out -- a guard who stays calm is
|
|
391
|
+
// a missed beat, a hostile bartender is a broken scene.
|
|
392
|
+
// ICE NEEDS NO FLAG: a "matrix" NPC is intrusion countermeasures
|
|
393
|
+
// and is treated as opposition automatically. Write it only to
|
|
394
|
+
// say FALSE, for the rare tame construct.
|
|
395
|
+
}],
|
|
396
|
+
"devices": [{
|
|
397
|
+
"name": string, "concept": string, // what it physically IS (a maglock, a keypad, a
|
|
398
|
+
// scanner, a ward) -- a THING, never a person
|
|
399
|
+
"room": string, // REQUIRED. A device always stands in ONE room -- the one you must be
|
|
400
|
+
// in to reach it. If it holds a door, declare that exit FROM THIS
|
|
401
|
+
// SAME ROOM with "device": "<this name>".
|
|
402
|
+
"kind": "lock"|"panel"|"ward", // lock = a mechanism: picked, breached, or opened by its
|
|
403
|
+
// key. panel = an interface: a code, or hacked. ward = a working:
|
|
404
|
+
// cast at or dispelled. EXACTLY these lowercase strings.
|
|
405
|
+
// A device is always a THING. A doorman or a checkpoint guard is
|
|
406
|
+
// NOT a device -- write them into npcs[] and name them in that
|
|
407
|
+
// exit's "guard"; people are got past socially, not picked.
|
|
408
|
+
"grantsItem"?: string // an items[].name with role "reward" -- what opening it hands
|
|
409
|
+
// over. Omit for a device that holds a door: the open way IS
|
|
410
|
+
// the reward.
|
|
411
|
+
}],
|
|
412
|
+
"items": [{
|
|
413
|
+
"name": string,
|
|
414
|
+
"role": "winCondition"|"reward"|"key"|"clue"|"loot"|"starting",
|
|
415
|
+
"category": string, // one of: Weapon, MeleeWeapon, RangedWeapon, Armor, BodyArmor,
|
|
416
|
+
// ArmorClothing, Helmet, Hat, LegArmor, ArmArmor, Gloves, Commlink,
|
|
417
|
+
// Cyberdeck, Drone, Credstick, CurrencyContainer, Key, Trinket,
|
|
418
|
+
// Document, Datachip, Vice, Book, Tool, Clothing, Glasses, Boots,
|
|
419
|
+
// Medical, StimPatch, SpellFormula, Grimoire, Focus, Ammunition,
|
|
420
|
+
// Food, Drink (consumables -- hot food heals stun, drink is liquid
|
|
421
|
+
// courage; fine as dressing in bars/kitchens, never as prizes),
|
|
422
|
+
// Cyberware (RARE prize loot -- an uninstalled augmentation part.
|
|
423
|
+
// Its name must be one of: ${AUGMENTATIONS.map(a => a.name).join(', ')};
|
|
424
|
+
// never one the runner already has installed, never role "starting")
|
|
425
|
+
"room"?: string, "heldBy"?: string, // exactly one of these for roles winCondition/key/
|
|
426
|
+
// clue/loot; NEITHER for roles reward/starting
|
|
427
|
+
"plane"?: "meat"|"matrix", // "matrix" = paydata: only in a hasNode room, category
|
|
428
|
+
// Datachip/Document. A matrix-plane winCondition item makes
|
|
429
|
+
// the job a datarun (its room MUST have ice).
|
|
430
|
+
"forPuzzle"?: string // role "clue" only: which puzzle's passphrase this clue reveals.
|
|
431
|
+
// EVERY puzzle needs exactly one clue item, category "Book",
|
|
432
|
+
// placed in a DIFFERENT room than that puzzle (door puzzles: any
|
|
433
|
+
// room). The clue's text is written in a later pass.
|
|
434
|
+
// ${isContinuation ? 'Role "starting" is FORBIDDEN in this scene -- the runner arrives fully equipped (see dossier).' : 'Role "starting" items are the fresh runner\'s basic kit.'}
|
|
435
|
+
}],
|
|
436
|
+
"winCondition": {
|
|
437
|
+
"item": string, // the items[].name with role "winCondition" -- MUST have room or heldBy
|
|
438
|
+
// set (never heldBy "toNpc" -- the player has to find it), and it is
|
|
439
|
+
// the central plot object of "story"
|
|
440
|
+
"toNpc": string, // an npcs[].name -- who wants it delivered
|
|
441
|
+
"payout": number, // the job's pay in nuyen; the engine actually transfers it -- half at
|
|
442
|
+
// the meet, the rest on delivery. ~2000-6000 for job #1, roughly half
|
|
443
|
+
// again more per job after that
|
|
444
|
+
"rewardItem"?: string // optional bonus: an items[].name with role "reward"
|
|
445
|
+
},
|
|
446
|
+
"client": string // the npcs[].name of the run's CLIENT -- see CLIENT below
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
CLIENT: every run has a CLIENT -- "a Mr. Johnson", the employer's deniable face, the person
|
|
450
|
+
who holds THE MEET (briefs the runner in person, negotiates the rate, pays the whole fee as
|
|
451
|
+
one lump sum on delivery). Invent a COVER NAME with corporate anonymity to it -- "Mr. Osprey",
|
|
452
|
+
"Ms. Veil", "Señora Marrow" -- never literally "Mr. Johnson" and never "${FIXER_NAME}" (that's
|
|
453
|
+
the runner's own fixer back home, who brokered this meet and is NOT in this scene). Station
|
|
454
|
+
the client in a REACHABLE room that makes a characterful meet spot -- a bar booth, a noodle
|
|
455
|
+
stall, the back seat of a parked limo, a chapel pew -- reachable from the start room WITHOUT
|
|
456
|
+
passing any puzzle, key, or hostile ground: the meet opens the job, it is never gated behind
|
|
457
|
+
it. The client MUST hold a Commlink-category item (role "loot", heldBy them) so the runner
|
|
458
|
+
can also call them, and the player must have a commlink available (${isContinuation ? 'the runner already carries their own -- see the dossier' : '"starting" role, or findable in a reachable room'}).
|
|
459
|
+
The client usually has no combat block -- they hired OUT the violence. They do NOT have to
|
|
460
|
+
be winCondition.toNpc (delivering to the client at the meet spot is one good shape; a
|
|
461
|
+
dead-drop to a different contact is another); any toNpc in an unreachable room needs a
|
|
462
|
+
commlink of their own too.
|
|
463
|
+
|
|
464
|
+
Hard structural rules (validated mechanically -- a violation is rejected):
|
|
465
|
+
- Every exits[].targetRoom, npcs[].startLocation, items[].room name a real rooms[].name;
|
|
466
|
+
every items[].heldBy names a real npcs[].name; every exits[].device names a real
|
|
467
|
+
puzzles[].name; every keyRequired names a real items[].name with role "key".
|
|
468
|
+
- "client" names a real npcs[].name, stationed in a REACHABLE room, holding a
|
|
469
|
+
Commlink-category item.
|
|
470
|
+
- Exactly one room has isStartRoom true, and it must be OUTDOOR street-level ground -- a
|
|
471
|
+
street, alley, plaza, lot, dock, yard -- with "outdoors": true AND a name/roomType that
|
|
472
|
+
says so. The runner
|
|
473
|
+
arrives by cab at that curb and hails the ride home from the same spot; an indoor start
|
|
474
|
+
room breaks the transport fiction. Never lock the start room's only exits behind keys
|
|
475
|
+
placed beyond them: every key must be reachable WITHOUT passing through the door it opens.
|
|
476
|
+
- A "reachable": false room never connects to a reachable one, and nothing the player must
|
|
477
|
+
retrieve (winCondition item, keys, clues) is ever placed in one.
|
|
478
|
+
- EVERY device names its own "room". If it holds a door, that door's exit is declared
|
|
479
|
+
from THAT SAME room with "device": "<its name>".
|
|
480
|
+
- EVERY WAY THROUGH IS FINDABLE -- no exceptions, and no dependence on who is playing:
|
|
481
|
+
* every "panel" gets a code AND exactly one "clue"-role Book item (forDevice set) in a
|
|
482
|
+
DIFFERENT room, reachable without passing the panel itself;
|
|
483
|
+
* every "lock" gets a "key"-role item placed in the world, reachable without passing
|
|
484
|
+
the lock itself.
|
|
485
|
+
Write the clue or the key in the same breath as the device. A runner with the matching
|
|
486
|
+
skill can still take the shortcut -- pick the lock, hack the panel -- but the found way
|
|
487
|
+
must exist for a runner who has neither.
|
|
488
|
+
- The winCondition item must not be heldBy winCondition.toNpc. If a matrix-plane item is the
|
|
489
|
+
winCondition item, its room has hasNode true AND a "matrix"-plane NPC stationed there.
|
|
490
490
|
- ${isContinuation
|
|
491
491
|
? 'ZERO "starting"-role items -- the runner arrives with everything they own. 4-6'
|
|
492
|
-
: '3-5 "starting"-role items (basics the runner doesn\'t already own -- see the dossier), 4-6'}
|
|
493
|
-
"loot"-role world items, one "clue" per panel and one "key" per lock.
|
|
494
|
-
|
|
495
|
-
Scope: ${scope}. Gritty noir Shadowrun tone: a client, a job, a debt, a betrayal -- and a
|
|
496
|
-
fresh premise, not a generic "corp blackmail vault" rerun.${repairNote(previousError)}
|
|
492
|
+
: '3-5 "starting"-role items (basics the runner doesn\'t already own -- see the dossier), 4-6'}
|
|
493
|
+
"loot"-role world items, one "clue" per panel and one "key" per lock.
|
|
494
|
+
|
|
495
|
+
Scope: ${scope}. Gritty noir Shadowrun tone: a client, a job, a debt, a betrayal -- and a
|
|
496
|
+
fresh premise, not a generic "corp blackmail vault" rerun.${repairNote(previousError)}
|
|
497
497
|
`.trim();
|
|
498
498
|
}
|
|
499
499
|
/** Shared context header for the detail passes: the fixed skeleton. */
|
|
500
500
|
function skeletonContext(skeleton) {
|
|
501
|
-
return `The scene's FIXED skeleton (all names are final -- reference them EXACTLY, never invent new ones):
|
|
501
|
+
return `The scene's FIXED skeleton (all names are final -- reference them EXACTLY, never invent new ones):
|
|
502
502
|
${JSON.stringify(skeleton, null, 1)}`;
|
|
503
503
|
}
|
|
504
504
|
export function buildRoomsPrompt(skeleton, previousError) {
|
|
505
|
-
return `
|
|
506
|
-
You are writing the room descriptions for a Shadowrun text adventure.
|
|
507
|
-
|
|
508
|
-
${skeletonContext(skeleton)}
|
|
509
|
-
|
|
510
|
-
For EVERY room in the skeleton, write evocative 2-3 sentence prose expanding its "concept" --
|
|
511
|
-
gritty cyberpunk noir, concrete sensory detail, no mechanics talk and no spoilers (never
|
|
512
|
-
reveal passphrases, hidden items, or plot twists in a room description).
|
|
513
|
-
Never write a CODE-SHAPED string into room prose -- no door numbers, unit numbers, serials,
|
|
514
|
-
or stencilled digits on a wall. The barriers' answers are the only codes in this world, and
|
|
515
|
-
a number in scenery is a number a player will carry to the nearest lock and type.
|
|
516
|
-
|
|
517
|
-
"At" SPOTS -- named places WITHIN a room the player can "move to":
|
|
518
|
-
- Give 2-4 "spots" ONLY where positioning matters (bars, markets, offices, warehouses --
|
|
519
|
-
social or large spaces). SKIP them for corridors, transit streets, and small
|
|
520
|
-
single-purpose rooms; most rooms should have none.
|
|
521
|
-
- Spot names are short concrete nouns ("bar", "booths", "loading dock", "desk") -- and
|
|
522
|
-
WEAVE each spot's name into that room's prose so the player can see it.
|
|
523
|
-
- Every room WITH spots must include one doorway spot (a name containing
|
|
524
|
-
door/entrance/entry/threshold/gate).
|
|
525
|
-
- At most ONE spot per spotted room may carry "tags": ["cover-partial"] (chest-high
|
|
526
|
-
cover) or ["cover-good"] (near-total concealment). Most need neither.
|
|
527
|
-
- "size": "tiny" | "small" | "medium" | "large" | "huge" -- how far apart the spots
|
|
528
|
-
stand and how far a shot travels: tiny is a closet or cubicle (a few meters across),
|
|
529
|
-
small a cramped room, medium a normal room, large a proper hall or bar floor, huge
|
|
530
|
-
a warehouse or atrium. Pick the smallest size that fits the room's concept.
|
|
531
|
-
- Optional "place" per spot -- where it stands: "north" | "south" | "east" | "west"
|
|
532
|
-
(that wall), "northeast corner" | "northwest corner" | "southeast corner" |
|
|
533
|
-
"southwest corner", or "center". Say this ONLY when the prose
|
|
534
|
-
actually puts it there ("the bar runs along the east wall"); leave it off otherwise
|
|
535
|
-
and the engine spreads the spot out on its own.
|
|
536
|
-
- Optional "levels" -- ONLY for a genuinely multi-level space the prose describes
|
|
537
|
-
(a catwalk over a warehouse floor, a mezzanine above a club, a balcony over an
|
|
538
|
-
atrium): [{ "name": "catwalk", "side": "north"|"south"|"east"|"west",
|
|
539
|
-
"kind": "catwalk"|"balcony"|"mezzanine" }], at most 2. A spot standing on one
|
|
540
|
-
carries "level": 1 (or 2 for the second). Most rooms have none.
|
|
541
|
-
- "npcSpots": station each npc whose startLocation is THIS room at the spot they'd
|
|
542
|
-
work from ({ "<npc name>": "<spot name>" }). Leave wanderers unstationed.
|
|
543
|
-
GIVE EACH ONE A DIFFERENT SPOT unless they genuinely share a post -- two people
|
|
544
|
-
on one tile read as standing shoulder to shoulder for no reason (a real report).
|
|
545
|
-
- Describe the SPACE ONLY -- never write PEOPLE or takeable goods into the prose.
|
|
546
|
-
People move, flee, fight, and die, and loot gets pocketed, but the description is
|
|
547
|
-
forever: a sentry written "blocking the way, arms crossed" reads absurd after he
|
|
548
|
-
ran (a real session). Presence is the engine's job -- "look" reports who stands
|
|
549
|
-
where live. The fixed stage (the spots, the architecture, the weather) is what
|
|
550
|
-
belongs in the prose.
|
|
551
|
-
- "itemSpots": anchor each item placed in THIS room to the spot it sits at
|
|
552
|
-
({ "<item name>": "<spot name>" }) -- a prize belongs at the guarded spot, not
|
|
553
|
-
floating in the doorway; loose scatter can stay unanchored.
|
|
554
|
-
- "exitSpots": which spot each exit leaves from ({ "<direction>": "<spot name>" }).
|
|
555
|
-
Give one to EVERY exit of a spotted room, and copy the spot name EXACTLY as you wrote
|
|
556
|
-
it in "spots" -- this is the only thing that tells the engine which way out a doorway
|
|
557
|
-
leads. Leave one off and the engine has to GUESS from the spot's name, which is how a
|
|
558
|
-
spot called "the back door" ended up bound to the stairs DOWN and stranded in the middle
|
|
559
|
-
of the floor. A vertical way out (stairs, a ladder, a floor hatch) needs a spot that
|
|
560
|
-
reads vertical, and a door needs one that reads like a door.
|
|
561
|
-
An exit blocked by a barrier deserves its
|
|
562
|
-
OWN named spot -- a back gate is not the entrance; two gates are two spots -- so the
|
|
563
|
-
player always knows which gate is which.
|
|
564
|
-
- ONE STORY: the prose and the spot data must agree. When the prose mentions a
|
|
565
|
-
stationed NPC, place them AT their npcSpots spot and use that spot's exact name
|
|
566
|
-
("under the awning" only if stationed at "awning") -- never "near the entrance" for
|
|
567
|
-
someone stationed elsewhere. An NPC whose job is watching a barrier stands AT that
|
|
568
|
-
barrier's exit spot.
|
|
569
|
-
|
|
570
|
-
${JSON_ONLY}
|
|
571
|
-
[ { "name": string, "description": string, "roomType": string,
|
|
572
|
-
"size"?: string, "spots"?: [ { "name": string, "tags"?: string[], "place"?: string, "level"?: number } ],
|
|
573
|
-
"npcSpots"?: { }, "itemSpots"?: { }, "exitSpots"?: { },
|
|
574
|
-
"levels"?: [ { "name": string, "side": string, "kind": string } ] } ]
|
|
575
|
-
One entry per skeleton room, "name" copied exactly.${repairNote(previousError)}
|
|
505
|
+
return `
|
|
506
|
+
You are writing the room descriptions for a Shadowrun text adventure.
|
|
507
|
+
|
|
508
|
+
${skeletonContext(skeleton)}
|
|
509
|
+
|
|
510
|
+
For EVERY room in the skeleton, write evocative 2-3 sentence prose expanding its "concept" --
|
|
511
|
+
gritty cyberpunk noir, concrete sensory detail, no mechanics talk and no spoilers (never
|
|
512
|
+
reveal passphrases, hidden items, or plot twists in a room description).
|
|
513
|
+
Never write a CODE-SHAPED string into room prose -- no door numbers, unit numbers, serials,
|
|
514
|
+
or stencilled digits on a wall. The barriers' answers are the only codes in this world, and
|
|
515
|
+
a number in scenery is a number a player will carry to the nearest lock and type.
|
|
516
|
+
|
|
517
|
+
"At" SPOTS -- named places WITHIN a room the player can "move to":
|
|
518
|
+
- Give 2-4 "spots" ONLY where positioning matters (bars, markets, offices, warehouses --
|
|
519
|
+
social or large spaces). SKIP them for corridors, transit streets, and small
|
|
520
|
+
single-purpose rooms; most rooms should have none.
|
|
521
|
+
- Spot names are short concrete nouns ("bar", "booths", "loading dock", "desk") -- and
|
|
522
|
+
WEAVE each spot's name into that room's prose so the player can see it.
|
|
523
|
+
- Every room WITH spots must include one doorway spot (a name containing
|
|
524
|
+
door/entrance/entry/threshold/gate).
|
|
525
|
+
- At most ONE spot per spotted room may carry "tags": ["cover-partial"] (chest-high
|
|
526
|
+
cover) or ["cover-good"] (near-total concealment). Most need neither.
|
|
527
|
+
- "size": "tiny" | "small" | "medium" | "large" | "huge" -- how far apart the spots
|
|
528
|
+
stand and how far a shot travels: tiny is a closet or cubicle (a few meters across),
|
|
529
|
+
small a cramped room, medium a normal room, large a proper hall or bar floor, huge
|
|
530
|
+
a warehouse or atrium. Pick the smallest size that fits the room's concept.
|
|
531
|
+
- Optional "place" per spot -- where it stands: "north" | "south" | "east" | "west"
|
|
532
|
+
(that wall), "northeast corner" | "northwest corner" | "southeast corner" |
|
|
533
|
+
"southwest corner", or "center". Say this ONLY when the prose
|
|
534
|
+
actually puts it there ("the bar runs along the east wall"); leave it off otherwise
|
|
535
|
+
and the engine spreads the spot out on its own.
|
|
536
|
+
- Optional "levels" -- ONLY for a genuinely multi-level space the prose describes
|
|
537
|
+
(a catwalk over a warehouse floor, a mezzanine above a club, a balcony over an
|
|
538
|
+
atrium): [{ "name": "catwalk", "side": "north"|"south"|"east"|"west",
|
|
539
|
+
"kind": "catwalk"|"balcony"|"mezzanine" }], at most 2. A spot standing on one
|
|
540
|
+
carries "level": 1 (or 2 for the second). Most rooms have none.
|
|
541
|
+
- "npcSpots": station each npc whose startLocation is THIS room at the spot they'd
|
|
542
|
+
work from ({ "<npc name>": "<spot name>" }). Leave wanderers unstationed.
|
|
543
|
+
GIVE EACH ONE A DIFFERENT SPOT unless they genuinely share a post -- two people
|
|
544
|
+
on one tile read as standing shoulder to shoulder for no reason (a real report).
|
|
545
|
+
- Describe the SPACE ONLY -- never write PEOPLE or takeable goods into the prose.
|
|
546
|
+
People move, flee, fight, and die, and loot gets pocketed, but the description is
|
|
547
|
+
forever: a sentry written "blocking the way, arms crossed" reads absurd after he
|
|
548
|
+
ran (a real session). Presence is the engine's job -- "look" reports who stands
|
|
549
|
+
where live. The fixed stage (the spots, the architecture, the weather) is what
|
|
550
|
+
belongs in the prose.
|
|
551
|
+
- "itemSpots": anchor each item placed in THIS room to the spot it sits at
|
|
552
|
+
({ "<item name>": "<spot name>" }) -- a prize belongs at the guarded spot, not
|
|
553
|
+
floating in the doorway; loose scatter can stay unanchored.
|
|
554
|
+
- "exitSpots": which spot each exit leaves from ({ "<direction>": "<spot name>" }).
|
|
555
|
+
Give one to EVERY exit of a spotted room, and copy the spot name EXACTLY as you wrote
|
|
556
|
+
it in "spots" -- this is the only thing that tells the engine which way out a doorway
|
|
557
|
+
leads. Leave one off and the engine has to GUESS from the spot's name, which is how a
|
|
558
|
+
spot called "the back door" ended up bound to the stairs DOWN and stranded in the middle
|
|
559
|
+
of the floor. A vertical way out (stairs, a ladder, a floor hatch) needs a spot that
|
|
560
|
+
reads vertical, and a door needs one that reads like a door.
|
|
561
|
+
An exit blocked by a barrier deserves its
|
|
562
|
+
OWN named spot -- a back gate is not the entrance; two gates are two spots -- so the
|
|
563
|
+
player always knows which gate is which.
|
|
564
|
+
- ONE STORY: the prose and the spot data must agree. When the prose mentions a
|
|
565
|
+
stationed NPC, place them AT their npcSpots spot and use that spot's exact name
|
|
566
|
+
("under the awning" only if stationed at "awning") -- never "near the entrance" for
|
|
567
|
+
someone stationed elsewhere. An NPC whose job is watching a barrier stands AT that
|
|
568
|
+
barrier's exit spot.
|
|
569
|
+
|
|
570
|
+
${JSON_ONLY}
|
|
571
|
+
[ { "name": string, "description": string, "roomType": string,
|
|
572
|
+
"size"?: string, "spots"?: [ { "name": string, "tags"?: string[], "place"?: string, "level"?: number } ],
|
|
573
|
+
"npcSpots"?: { }, "itemSpots"?: { }, "exitSpots"?: { },
|
|
574
|
+
"levels"?: [ { "name": string, "side": string, "kind": string } ] } ]
|
|
575
|
+
One entry per skeleton room, "name" copied exactly.${repairNote(previousError)}
|
|
576
576
|
`.trim();
|
|
577
577
|
}
|
|
578
578
|
export function buildNpcsPrompt(skeleton, previousError) {
|
|
579
|
-
return `
|
|
580
|
-
You are writing the characters for a Shadowrun text adventure.
|
|
581
|
-
|
|
582
|
-
${skeletonContext(skeleton)}
|
|
583
|
-
|
|
584
|
-
For EVERY npc in the skeleton, expand its "concept":
|
|
585
|
-
- "description": private GM notes on personality/motives (never shown verbatim to the player).
|
|
586
|
-
- "dialogue": 2-4 short in-character lines. For "${skeleton.client}" (the CLIENT -- the
|
|
587
|
-
Johnson holding the meet) this is the REAL briefing -- the objective (the winCondition
|
|
588
|
-
item), where to start looking, the stakes, and the exact payout figure from the skeleton
|
|
589
|
-
(the engine pays exactly that number, as one lump sum on delivery). Their private
|
|
590
|
-
"description" should read like a client rep's dossier -- the employer's deniable face, a
|
|
591
|
-
Johnson buying deniability. When referring to places, use EXACT room names from the
|
|
592
|
-
skeleton -- never invent locations.
|
|
593
|
-
NEVER write a CREDENTIAL into "dialogue" or "description" -- no door code, safe
|
|
594
|
-
combination, passphrase, or key-word, not even a hint at the digits. This stage runs in
|
|
595
|
-
PARALLEL with the stage that writes the pass each barrier actually accepts, so you
|
|
596
|
-
cannot see those passes, and any credential you write here is one you invented: the lock
|
|
597
|
-
will refuse it and the player will burn a run trusting it. An NPC may say that a code
|
|
598
|
-
EXISTS, who holds it, or where it's written down -- never what it is. The engine hands
|
|
599
|
-
the real pass to whoever genuinely holds it, at runtime (see npc.ts).
|
|
600
|
-
- "combat": OPTIONAL attribute block -- omit for anyone who'd never fight. Each attribute
|
|
601
|
-
1-8 (soft civilian 1-2, street-level 3-4, hardened enforcer 5-6, elite 7-8):
|
|
602
|
-
{ "body", "agility", "reaction", "combatSkill", "strength"?, "willpower"?, "logic"?,
|
|
603
|
-
"intuition"?, "charisma"?, "magic"?, "resonance"?, "adept"?,
|
|
604
|
-
"skills"?: { "firearms"?, "blades"?, "clubs"?, "unarmed"?, "sneaking"?, "perception"?, "palming"?,
|
|
605
|
-
"hacking"?, "cybercombat"?, "electronic-warfare"?, "hardware"?, "locksmith"?, "computer"?,
|
|
606
|
-
"negotiation"?, "con"?, "intimidation"?, "etiquette"?, "leadership"?, "performance"?, "disguise"?,
|
|
607
|
-
"medicine"?, "assensing"?, "counterspelling"?, "conjuring"?, "gunnery"? } }
|
|
608
|
-
"magic" 3-6 ONLY for a genuinely Awakened concept; "plane":"matrix" ice fights with
|
|
609
|
-
logic 4-7; give combat blocks to anyone the story expects in a firefight. "skills" is
|
|
610
|
-
optional flavor for defined concepts (a sharpshooter's "firearms": 6).
|
|
611
|
-
|
|
612
|
-
${JSON_ONLY}
|
|
613
|
-
[ { "name": string, "description": string, "dialogue": string[], "combat"?: { ... } } ]
|
|
614
|
-
One entry per skeleton npc, "name" copied exactly.${repairNote(previousError)}
|
|
579
|
+
return `
|
|
580
|
+
You are writing the characters for a Shadowrun text adventure.
|
|
581
|
+
|
|
582
|
+
${skeletonContext(skeleton)}
|
|
583
|
+
|
|
584
|
+
For EVERY npc in the skeleton, expand its "concept":
|
|
585
|
+
- "description": private GM notes on personality/motives (never shown verbatim to the player).
|
|
586
|
+
- "dialogue": 2-4 short in-character lines. For "${skeleton.client}" (the CLIENT -- the
|
|
587
|
+
Johnson holding the meet) this is the REAL briefing -- the objective (the winCondition
|
|
588
|
+
item), where to start looking, the stakes, and the exact payout figure from the skeleton
|
|
589
|
+
(the engine pays exactly that number, as one lump sum on delivery). Their private
|
|
590
|
+
"description" should read like a client rep's dossier -- the employer's deniable face, a
|
|
591
|
+
Johnson buying deniability. When referring to places, use EXACT room names from the
|
|
592
|
+
skeleton -- never invent locations.
|
|
593
|
+
NEVER write a CREDENTIAL into "dialogue" or "description" -- no door code, safe
|
|
594
|
+
combination, passphrase, or key-word, not even a hint at the digits. This stage runs in
|
|
595
|
+
PARALLEL with the stage that writes the pass each barrier actually accepts, so you
|
|
596
|
+
cannot see those passes, and any credential you write here is one you invented: the lock
|
|
597
|
+
will refuse it and the player will burn a run trusting it. An NPC may say that a code
|
|
598
|
+
EXISTS, who holds it, or where it's written down -- never what it is. The engine hands
|
|
599
|
+
the real pass to whoever genuinely holds it, at runtime (see npc.ts).
|
|
600
|
+
- "combat": OPTIONAL attribute block -- omit for anyone who'd never fight. Each attribute
|
|
601
|
+
1-8 (soft civilian 1-2, street-level 3-4, hardened enforcer 5-6, elite 7-8):
|
|
602
|
+
{ "body", "agility", "reaction", "combatSkill", "strength"?, "willpower"?, "logic"?,
|
|
603
|
+
"intuition"?, "charisma"?, "magic"?, "resonance"?, "adept"?,
|
|
604
|
+
"skills"?: { "firearms"?, "blades"?, "clubs"?, "unarmed"?, "sneaking"?, "perception"?, "palming"?,
|
|
605
|
+
"hacking"?, "cybercombat"?, "electronic-warfare"?, "hardware"?, "locksmith"?, "computer"?,
|
|
606
|
+
"negotiation"?, "con"?, "intimidation"?, "etiquette"?, "leadership"?, "performance"?, "disguise"?,
|
|
607
|
+
"medicine"?, "assensing"?, "counterspelling"?, "conjuring"?, "gunnery"? } }
|
|
608
|
+
"magic" 3-6 ONLY for a genuinely Awakened concept; "plane":"matrix" ice fights with
|
|
609
|
+
logic 4-7; give combat blocks to anyone the story expects in a firefight. "skills" is
|
|
610
|
+
optional flavor for defined concepts (a sharpshooter's "firearms": 6).
|
|
611
|
+
|
|
612
|
+
${JSON_ONLY}
|
|
613
|
+
[ { "name": string, "description": string, "dialogue": string[], "combat"?: { ... } } ]
|
|
614
|
+
One entry per skeleton npc, "name" copied exactly.${repairNote(previousError)}
|
|
615
615
|
`.trim();
|
|
616
616
|
}
|
|
617
617
|
export function buildDevicesPrompt(skeleton, previousError) {
|
|
618
618
|
if (skeleton.devices.length === 0)
|
|
619
619
|
return '';
|
|
620
|
-
return `
|
|
621
|
-
You are writing the DEVICES for a Shadowrun text adventure. A device is a THING that is
|
|
622
|
-
shut and can be opened -- a lock, a panel, a ward. Never a person.
|
|
623
|
-
|
|
624
|
-
${skeletonContext(skeleton)}
|
|
625
|
-
|
|
626
|
-
For EVERY device in the skeleton, expand its "concept" (what it physically is -- that's
|
|
627
|
-
fixed, as is its "kind"):
|
|
628
|
-
- "description": what the player sees on approaching it, shown verbatim -- PLAIN and
|
|
629
|
-
concrete, never a cryptic riddle-poem. Describe it as a THING: what it is, how it's
|
|
630
|
-
secured, and what it visibly expects. Write NO people into it -- no guards, no dialogue,
|
|
631
|
-
no "they demand": anyone standing watch does their own demanding in play, in their own
|
|
632
|
-
voice. Never state the credential itself, and never write ANY OTHER number into it -- no
|
|
633
|
-
unit number, no model number, no serial, no floor number. Every code-shaped string a
|
|
634
|
-
player reads is one they will go and type.
|
|
635
|
-
- "code": ONLY for kind "panel", and REQUIRED for one. The exact credential it accepts, and
|
|
636
|
-
it MUST BE THE SAME KIND OF THING your "description" says the panel wants. This is the
|
|
637
|
-
single most confusing thing in the game when it drifts: a player who reads "a keypad with
|
|
638
|
-
worn digits" and then finds "double or nothing" written in a notebook does not believe
|
|
639
|
-
they have the code, and goes looking for a number that does not exist.
|
|
640
|
-
* If the description shows a NUMERIC pad, dial, or counter -- digits only, 3-6 of them,
|
|
641
|
-
no spaces ("470912"). Then it really does take a number.
|
|
642
|
-
* Otherwise (an intercom, a voice-gate, a spoken passphrase, a name, a sign reading
|
|
643
|
-
"password") -- ALL LOWERCASE, 2-5 plain words, no punctuation.
|
|
644
|
-
Either way a concrete literal thing, never wordplay.
|
|
645
|
-
OMIT "code" for a "lock" or a "ward". You cannot talk at a mechanical lock, and a ward
|
|
646
|
-
answers to the Art -- neither has anything to hear a phrase with, so a code on one is a
|
|
647
|
-
promise the game cannot keep.
|
|
648
|
-
- "openMessage" / "refuseMessage": one sentence each, in-fiction.
|
|
649
|
-
- "guardsExit": ONLY when the concept is physically TERRAIN blocking a passage -- rubble, a
|
|
650
|
-
collapsed rack, a security shutter across a walkway -- name the exact "direction" of one
|
|
651
|
-
of its own room's exits that it seals. A device on a container, safe, or item pickup
|
|
652
|
-
guards nothing physical: leave it unset.
|
|
653
|
-
- "footprint": "small" | "medium" | "large" -- how much of the room the blockage occupies.
|
|
654
|
-
Only meaningful alongside "guardsExit".
|
|
655
|
-
|
|
656
|
-
${JSON_ONLY}
|
|
657
|
-
[ { "name": string, "description": string, "code"?: string, "openMessage": string,
|
|
658
|
-
"refuseMessage": string, "guardsExit"?: string, "footprint"?: string } ]
|
|
659
|
-
One entry per skeleton device, "name" copied exactly.${repairNote(previousError)}
|
|
620
|
+
return `
|
|
621
|
+
You are writing the DEVICES for a Shadowrun text adventure. A device is a THING that is
|
|
622
|
+
shut and can be opened -- a lock, a panel, a ward. Never a person.
|
|
623
|
+
|
|
624
|
+
${skeletonContext(skeleton)}
|
|
625
|
+
|
|
626
|
+
For EVERY device in the skeleton, expand its "concept" (what it physically is -- that's
|
|
627
|
+
fixed, as is its "kind"):
|
|
628
|
+
- "description": what the player sees on approaching it, shown verbatim -- PLAIN and
|
|
629
|
+
concrete, never a cryptic riddle-poem. Describe it as a THING: what it is, how it's
|
|
630
|
+
secured, and what it visibly expects. Write NO people into it -- no guards, no dialogue,
|
|
631
|
+
no "they demand": anyone standing watch does their own demanding in play, in their own
|
|
632
|
+
voice. Never state the credential itself, and never write ANY OTHER number into it -- no
|
|
633
|
+
unit number, no model number, no serial, no floor number. Every code-shaped string a
|
|
634
|
+
player reads is one they will go and type.
|
|
635
|
+
- "code": ONLY for kind "panel", and REQUIRED for one. The exact credential it accepts, and
|
|
636
|
+
it MUST BE THE SAME KIND OF THING your "description" says the panel wants. This is the
|
|
637
|
+
single most confusing thing in the game when it drifts: a player who reads "a keypad with
|
|
638
|
+
worn digits" and then finds "double or nothing" written in a notebook does not believe
|
|
639
|
+
they have the code, and goes looking for a number that does not exist.
|
|
640
|
+
* If the description shows a NUMERIC pad, dial, or counter -- digits only, 3-6 of them,
|
|
641
|
+
no spaces ("470912"). Then it really does take a number.
|
|
642
|
+
* Otherwise (an intercom, a voice-gate, a spoken passphrase, a name, a sign reading
|
|
643
|
+
"password") -- ALL LOWERCASE, 2-5 plain words, no punctuation.
|
|
644
|
+
Either way a concrete literal thing, never wordplay.
|
|
645
|
+
OMIT "code" for a "lock" or a "ward". You cannot talk at a mechanical lock, and a ward
|
|
646
|
+
answers to the Art -- neither has anything to hear a phrase with, so a code on one is a
|
|
647
|
+
promise the game cannot keep.
|
|
648
|
+
- "openMessage" / "refuseMessage": one sentence each, in-fiction.
|
|
649
|
+
- "guardsExit": ONLY when the concept is physically TERRAIN blocking a passage -- rubble, a
|
|
650
|
+
collapsed rack, a security shutter across a walkway -- name the exact "direction" of one
|
|
651
|
+
of its own room's exits that it seals. A device on a container, safe, or item pickup
|
|
652
|
+
guards nothing physical: leave it unset.
|
|
653
|
+
- "footprint": "small" | "medium" | "large" -- how much of the room the blockage occupies.
|
|
654
|
+
Only meaningful alongside "guardsExit".
|
|
655
|
+
|
|
656
|
+
${JSON_ONLY}
|
|
657
|
+
[ { "name": string, "description": string, "code"?: string, "openMessage": string,
|
|
658
|
+
"refuseMessage": string, "guardsExit"?: string, "footprint"?: string } ]
|
|
659
|
+
One entry per skeleton device, "name" copied exactly.${repairNote(previousError)}
|
|
660
660
|
`.trim();
|
|
661
661
|
}
|
|
662
662
|
export function buildItemsPrompt(skeleton, devices, previousError) {
|
|
@@ -667,36 +667,36 @@ export function buildItemsPrompt(skeleton, devices, previousError) {
|
|
|
667
667
|
return `- "${i.name}" is the clue for device "${i.forDevice}": its "details" text MUST contain the exact code "${code ?? 'UNKNOWN'}" verbatim (a diary entry, scrawled note, or manual that gives it away once read).`;
|
|
668
668
|
})
|
|
669
669
|
.join('\n');
|
|
670
|
-
return `
|
|
671
|
-
You are writing the items for a Shadowrun text adventure.
|
|
672
|
-
|
|
673
|
-
${skeletonContext(skeleton)}
|
|
674
|
-
|
|
675
|
-
For EVERY item in the skeleton, expand it into a full item ("name" and "category" are fixed):
|
|
676
|
-
- "description": one sentence of always-visible flavor.
|
|
677
|
-
- "details": deeper text shown on close examination or reading. CLUE ITEMS (below) put
|
|
678
|
-
their puzzle's credential HERE -- never in "description".
|
|
679
|
-
- "size": "Tiny"|"Small"|"Medium"|"Large"|"VeryLarge"; "rating": "Common"|"Standard"|"Rare"|
|
|
680
|
-
"Legendary"|"MilitaryGrade" (rating scales combat/heal/deck power -- reserve Rare+ for
|
|
681
|
-
earned rewards); "shape"/"color"/"texture": short literals; "weight": realistic kilograms
|
|
682
|
-
(0.05 chip, ~2 pistol, ~4 armor); "currencyAmount" only for Credstick/CurrencyContainer.
|
|
683
|
-
|
|
684
|
-
Clue requirements (mechanically validated -- the exact phrase must appear in "details"):
|
|
685
|
-
${clueSpecs || '- (no clue items in this scene)'}
|
|
686
|
-
|
|
687
|
-
THE ONLY CODES IN THIS WORLD are the clue phrases listed just above. No other item may
|
|
688
|
-
carry a code-shaped string -- no serial numbers, no model numbers, no unit or locker or
|
|
689
|
-
apartment numbers, no "access code" on a scrap of paper that isn't a listed clue. A player
|
|
690
|
-
who reads a number writes it down and takes it to the nearest lock; a second plausible
|
|
691
|
-
number is indistinguishable from the real one and it costs them the run. Quantities and
|
|
692
|
-
prices are fine (a clip "holds 10 rounds", a stick "carries 2000 nuyen") -- those read as
|
|
693
|
-
amounts, not credentials.
|
|
694
|
-
|
|
695
|
-
${JSON_ONLY}
|
|
696
|
-
[ { "name": string, "description": string, "size": string, "category": string,
|
|
697
|
-
"shape": string, "color": string, "texture": string, "rating": string, "weight": number,
|
|
698
|
-
"currencyAmount"?: number, "details"?: string, "transferable"?: boolean } ]
|
|
699
|
-
One entry per skeleton item, "name" and "category" copied exactly.${repairNote(previousError)}
|
|
670
|
+
return `
|
|
671
|
+
You are writing the items for a Shadowrun text adventure.
|
|
672
|
+
|
|
673
|
+
${skeletonContext(skeleton)}
|
|
674
|
+
|
|
675
|
+
For EVERY item in the skeleton, expand it into a full item ("name" and "category" are fixed):
|
|
676
|
+
- "description": one sentence of always-visible flavor.
|
|
677
|
+
- "details": deeper text shown on close examination or reading. CLUE ITEMS (below) put
|
|
678
|
+
their puzzle's credential HERE -- never in "description".
|
|
679
|
+
- "size": "Tiny"|"Small"|"Medium"|"Large"|"VeryLarge"; "rating": "Common"|"Standard"|"Rare"|
|
|
680
|
+
"Legendary"|"MilitaryGrade" (rating scales combat/heal/deck power -- reserve Rare+ for
|
|
681
|
+
earned rewards); "shape"/"color"/"texture": short literals; "weight": realistic kilograms
|
|
682
|
+
(0.05 chip, ~2 pistol, ~4 armor); "currencyAmount" only for Credstick/CurrencyContainer.
|
|
683
|
+
|
|
684
|
+
Clue requirements (mechanically validated -- the exact phrase must appear in "details"):
|
|
685
|
+
${clueSpecs || '- (no clue items in this scene)'}
|
|
686
|
+
|
|
687
|
+
THE ONLY CODES IN THIS WORLD are the clue phrases listed just above. No other item may
|
|
688
|
+
carry a code-shaped string -- no serial numbers, no model numbers, no unit or locker or
|
|
689
|
+
apartment numbers, no "access code" on a scrap of paper that isn't a listed clue. A player
|
|
690
|
+
who reads a number writes it down and takes it to the nearest lock; a second plausible
|
|
691
|
+
number is indistinguishable from the real one and it costs them the run. Quantities and
|
|
692
|
+
prices are fine (a clip "holds 10 rounds", a stick "carries 2000 nuyen") -- those read as
|
|
693
|
+
amounts, not credentials.
|
|
694
|
+
|
|
695
|
+
${JSON_ONLY}
|
|
696
|
+
[ { "name": string, "description": string, "size": string, "category": string,
|
|
697
|
+
"shape": string, "color": string, "texture": string, "rating": string, "weight": number,
|
|
698
|
+
"currencyAmount"?: number, "details"?: string, "transferable"?: boolean } ]
|
|
699
|
+
One entry per skeleton item, "name" and "category" copied exactly.${repairNote(previousError)}
|
|
700
700
|
`.trim();
|
|
701
701
|
}
|
|
702
702
|
// ========================= Parse / validate / assemble ====================
|