quoxcli 0.2.0 → 0.4.0
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/LICENSE +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +46 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +7 -0
- package/lib/api/endpoints/assistants.js +12 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +14 -0
- package/lib/api/endpoints/contactsq.js +6 -0
- package/lib/api/endpoints/conversations.js +14 -0
- package/lib/api/endpoints/devServerRegistry.js +25 -0
- package/lib/api/endpoints/engagement.js +26 -0
- package/lib/api/endpoints/evidence.js +20 -0
- package/lib/api/endpoints/executions.js +13 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/files.js +14 -0
- package/lib/api/endpoints/hat.js +44 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +26 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +179 -0
- package/lib/api/endpoints/objectives.js +12 -0
- package/lib/api/endpoints/plugins.js +18 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/quoxplan.js +7 -0
- package/lib/api/endpoints/quoxseo.js +12 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/skills.js +20 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/teams.js +2 -2
- package/lib/api/endpoints/triggers.js +26 -0
- package/lib/api/endpoints/v1.js +120 -0
- package/lib/api/endpoints/ward.js +7 -0
- package/lib/api/endpoints/workflows.js +7 -0
- package/lib/api/endpoints/x.js +8 -0
- package/lib/commands/actions.js +131 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +170 -27
- package/lib/commands/agentAttach.js +140 -13
- package/lib/commands/approvals.js +78 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +146 -0
- package/lib/commands/assistant.js +59 -4
- package/lib/commands/auth.js +8 -2
- package/lib/commands/backup.js +8 -4
- package/lib/commands/blog.js +110 -0
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +180 -26
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +35 -38
- package/lib/commands/contacts.js +134 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/conversation.js +35 -4
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +28 -7
- package/lib/commands/devResident.js +15 -3
- package/lib/commands/devSupervisor.js +13 -1
- package/lib/commands/discord.js +75 -39
- package/lib/commands/done.js +98 -28
- package/lib/commands/engagement.js +107 -6
- package/lib/commands/evidence.js +154 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +336 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +254 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/kuma.js +1050 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +52 -155
- package/lib/commands/loop.js +291 -0
- package/lib/commands/lucid.js +10 -4
- package/lib/commands/memory.js +85 -8
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +69 -2
- package/lib/commands/monitoring.js +1 -1
- package/lib/commands/motion.js +2 -2
- package/lib/commands/nucleus.js +48 -17
- package/lib/commands/objective.js +59 -1
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +30 -3
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +38 -0
- package/lib/commands/plugin.js +110 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/provider.js +217 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +69 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/research.js +79 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +367 -0
- package/lib/commands/run.js +82 -10
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +405 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +68 -9
- package/lib/commands/slot.js +260 -7
- package/lib/commands/status.js +1 -1
- package/lib/commands/team.js +3 -1
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +133 -45
- package/lib/commands/trace.js +12 -7
- package/lib/commands/trigger.js +95 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +31 -8
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +34 -3
- package/lib/commands/volt.js +45 -3
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +73 -21
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +189 -32
- package/lib/commands/x.js +13 -4
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actingAs.js +2 -2
- package/lib/core/actionItems.js +115 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +164 -13
- package/lib/core/callerSource.js +1 -1
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +152 -35
- package/lib/core/commandRegistrars.js +130 -0
- package/lib/core/config.js +143 -25
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +105 -5
- package/lib/core/governedSession.js +1 -3
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +533 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +11 -11
- package/lib/core/peek.js +126 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentSeed.js +3 -1
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionHeartbeat.js +12 -8
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/shortcut.js +83 -0
- package/lib/core/slotKeeper.js +248 -3
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +71 -2
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +366 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/dragon/companionView.js +34 -0
- package/lib/dragon/frames.js +118 -0
- package/lib/dragon/jobs.js +123 -0
- package/lib/dragon/renderWorker.js +15 -0
- package/lib/dragon/select.js +72 -0
- package/lib/evidence/bundleExport.js +47 -0
- package/lib/evidence/bundleVerify.js +77 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +45 -58
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/quoxon/EXTENSIONS.md +23 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +690 -25
- package/lib/quoxon/app.js +3586 -250
- package/lib/quoxon/approvalFacts.js +96 -0
- package/lib/quoxon/approvalSentence.js +44 -0
- package/lib/quoxon/approvalsView.js +384 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +124 -0
- package/lib/quoxon/commands.js +429 -39
- package/lib/quoxon/connect.js +210 -23
- package/lib/quoxon/connectCard.js +19 -2
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/copy.js +4 -3
- package/lib/quoxon/countdownLine.js +20 -0
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +46 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +192 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonCompanion.js +255 -0
- package/lib/quoxon/dragonModes.js +151 -0
- package/lib/quoxon/dragonMoments.js +82 -0
- package/lib/quoxon/dragonReads.js +68 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +2 -2
- package/lib/quoxon/events.js +266 -26
- package/lib/quoxon/extensions.js +276 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +21 -7
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/inboxView.js +79 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +51 -8
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/loopCard.js +456 -0
- package/lib/quoxon/loopCards.js +180 -0
- package/lib/quoxon/loopStream.js +87 -0
- package/lib/quoxon/main.js +101 -160
- package/lib/quoxon/markdown.js +45 -21
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +132 -26
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +3 -2
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/mouse.js +222 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/fetch.js +17 -5
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +46 -7
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +12 -4
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/view.js +11 -0
- package/lib/quoxon/onboarding/copy.js +32 -4
- package/lib/quoxon/onboarding/credential.js +17 -4
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +15 -0
- package/lib/quoxon/onboarding/index.js +34 -7
- package/lib/quoxon/onboarding/installRunner.js +64 -5
- package/lib/quoxon/onboarding/preflight.js +17 -3
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +59 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +12 -5
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +20 -51
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +62 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +75 -37
- package/lib/quoxon/render.js +740 -124
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +117 -17
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +954 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +182 -2
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +94 -0
- package/lib/quoxon/slash/cliLeaves.json +1023 -0
- package/lib/quoxon/slash/compact.js +1 -1
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/evidenceCmd.js +34 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +13 -8
- package/lib/quoxon/slash/listView.js +3 -1
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +22 -8
- package/lib/quoxon/slash/paletteController.js +117 -7
- package/lib/quoxon/slash/panels.js +21 -6
- package/lib/quoxon/slash/picker.js +20 -2
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +95 -13
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +16 -6
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/todoCmd.js +122 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/mapping.js +2 -1
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +90 -0
- package/lib/quoxon/term.js +46 -11
- package/lib/quoxon/textBuffer.js +176 -2
- package/lib/quoxon/theme.js +58 -25
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +42 -4
- package/lib/quoxon/web/qface.js +18 -10
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +134 -0
- package/lib/quoxon/windowOrg.js +123 -0
- package/lib/server/devServers.js +137 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +5 -1
- package/vendor/dragonlab/SOURCE +4 -0
- package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
- package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
- package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
- package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
- package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
- package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
- package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
- package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
- package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
- package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
- package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
- package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
- package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
- package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
- package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
- package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
- package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
- package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
- package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
- package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
- package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
- package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
- package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
- package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
- package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
- package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
- package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
- package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
- package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
- package/vendor/dragonlab/lib/power-effects.js +294 -0
- package/vendor/dragonlab/lib/power-registry.js +74 -0
- package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
- package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
- package/lib/core/websiteSession.js +0 -148
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Original Sentinel shield-face icon at small terminal art sizes.
|
|
3
|
+
* Source: design/stage2/refinement/sentinel-a-tiny-shield-icon.svg.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately separate icon geometry, not a crushed full dragon.
|
|
6
|
+
* SVG paths are resampled to 2×4 Braille dots using the actual qart packer.
|
|
7
|
+
* No backgrounds, no terminal control codes, deterministic in colour/mono.
|
|
8
|
+
*/
|
|
9
|
+
import {makeField,fieldToCells} from '../../vendor/quox-dragon/qart.js';
|
|
10
|
+
import {flattenPath,sampleShape} from './svg-coverage.js';
|
|
11
|
+
|
|
12
|
+
const SHAPE=Object.freeze({
|
|
13
|
+
leftHorn:'M12 15 Q9 12 10 4 Q15 7 17 13 Z',
|
|
14
|
+
rightHorn:'M31 13 Q36 8 37 4 Q40 12 37 17 Z',
|
|
15
|
+
shield:'M9 14 Q23 7 39 14 L37 28 Q34 40 24 44 Q13 39 10 28 Z',
|
|
16
|
+
smile:'M20 35 Q24 38 28 34',
|
|
17
|
+
});
|
|
18
|
+
const rgba={
|
|
19
|
+
horn:[141,218,255],body:[87,153,238],rim:[20,39,68],
|
|
20
|
+
eye:[255,253,246],cheek:[250,147,183],
|
|
21
|
+
};
|
|
22
|
+
function ellipse(cx,cy,rx,ry){
|
|
23
|
+
const pts=[];
|
|
24
|
+
for(let k=0;k<72;k++){const th=k*Math.PI*2/72;pts.push([cx+rx*Math.cos(th),cy+ry*Math.sin(th)]);}
|
|
25
|
+
return [pts];
|
|
26
|
+
}
|
|
27
|
+
function mask(renderer,source,paint,stroke=0){
|
|
28
|
+
renderer(source,paint,stroke);
|
|
29
|
+
}
|
|
30
|
+
function showBounds(frame){
|
|
31
|
+
let x0=Infinity,x1=-1,y0=Infinity,y1=-1,n=0;
|
|
32
|
+
for(let y=0;y<frame.length;y++)for(let x=0;x<frame[y].length;x++)
|
|
33
|
+
if(frame[y][x].ch!==' '&&frame[y][x].ch!=='\u2800'){
|
|
34
|
+
n++;x0=Math.min(x0,x);x1=Math.max(x1,x);y0=Math.min(y0,y);y1=Math.max(y1,y);
|
|
35
|
+
}
|
|
36
|
+
return n?{x0,x1,y0,y1,cells:n}:null;
|
|
37
|
+
}
|
|
38
|
+
export function renderSentinelIcon({cols=16,rows=8,mono=false,mood='cute'}={}){
|
|
39
|
+
if(!Number.isSafeInteger(cols)||!Number.isSafeInteger(rows)||cols<=0||rows<=0)
|
|
40
|
+
return {ok:false,code:'INVALID_VIEWPORT',reason:'Icon viewport needs positive cell dimensions.'};
|
|
41
|
+
if(cols>200||rows>72)return {ok:false,code:'TOO_LARGE',reason:'Icon viewport exceeds supported limit.'};
|
|
42
|
+
if(cols<12||rows<6)return {ok:false,code:'TEXT_FALLBACK',reason:'Use a shield symbol plus Sentinel label.'};
|
|
43
|
+
if(!['cute','alert','warden'].includes(mood))
|
|
44
|
+
return {ok:false,code:'UNKNOWN_MOOD',reason:'Icon mood must be cute, alert or warden.'};
|
|
45
|
+
const W=cols*2,H=rows*4;
|
|
46
|
+
const scale=Math.min((W-4)/48,(H-4)/48,1.5);
|
|
47
|
+
const ox=(W-48*scale)/2,oy=(H-48*scale)/2;
|
|
48
|
+
const field=makeField(W,H),layers=new Float32Array(W*H),rgb=new Uint8Array(W*H*3);
|
|
49
|
+
const stamp=(polys,color,z,erase=false,stroke=0)=>{
|
|
50
|
+
sampleShape(polys,{W,H,scale,ox,oy,stroke},(x,y)=>{
|
|
51
|
+
const p=y*W+x;if(z<layers[p])return;
|
|
52
|
+
layers[p]=z;field.v[p]=erase?0:1;
|
|
53
|
+
if(!erase)rgb.set(color,p*3);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const P=(p)=>flattenPath(p);
|
|
57
|
+
stamp(P(SHAPE.leftHorn),rgba.horn,1);
|
|
58
|
+
stamp(P(SHAPE.rightHorn),rgba.horn,1);
|
|
59
|
+
stamp(P(SHAPE.shield),rgba.body,2);
|
|
60
|
+
const angry=mood==='warden',watch=mood!=='cute';
|
|
61
|
+
for(const [cx,cy,rx,ry,px,py] of [
|
|
62
|
+
[18,23,5.7,6.8,20,23],[31,23,5.2,6.2,33,23],
|
|
63
|
+
]){
|
|
64
|
+
// Physical dark margin must cut out the solid body first. Whole Braille
|
|
65
|
+
// cells containing white eyes are given eye-only colour ownership below.
|
|
66
|
+
stamp(ellipse(cx,cy,rx+1.0,ry+1.0),rgba.rim,3,true);
|
|
67
|
+
stamp(ellipse(cx,cy,rx,ry),rgba.eye,4);
|
|
68
|
+
stamp(ellipse(px,py+(watch?.5:0),angry?2.2:2.4,angry?3.0:3.3),rgba.rim,5,true);
|
|
69
|
+
if(angry){
|
|
70
|
+
// The tiny expression follows the same inward-V white cut,
|
|
71
|
+
// only after the stronger eye white has been drawn.
|
|
72
|
+
const side=cx<25?-1:1;
|
|
73
|
+
const triangle=[];
|
|
74
|
+
for(let x=0;x<8;x++){
|
|
75
|
+
const outside=cx+side*(-rx+x*.92);
|
|
76
|
+
triangle.push([outside,cy-ry-.2+(7-x)*.42]);
|
|
77
|
+
}
|
|
78
|
+
stamp([triangle],rgba.rim,6,true);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if(mood==='cute')stamp(P(SHAPE.smile),rgba.rim,6,true,1.5);
|
|
82
|
+
else stamp(P('M20 35 L28 35'),rgba.rim,6,true,1.4);
|
|
83
|
+
|
|
84
|
+
const frame=fieldToCells(field,{color:false,quant:false,tint:rgba.body});
|
|
85
|
+
// Recolour complete Braille cells based on the majority surviving pixels.
|
|
86
|
+
// Unlike direct stacking, there can be no blue body ink in an eye-white
|
|
87
|
+
// terminal cell. Mono reproduces the exact same dot mask.
|
|
88
|
+
for(let cy=0;cy<rows;cy++)for(let cx=0;cx<cols;cx++){
|
|
89
|
+
const cell=frame[cy][cx];cell.bg=null;
|
|
90
|
+
if(mono||cell.ch===' '){cell.rgb=null;continue;}
|
|
91
|
+
const counts=new Map();
|
|
92
|
+
for(let dy=0;dy<4;dy++)for(let dx=0;dx<2;dx++){
|
|
93
|
+
const p=(cy*4+dy)*W+cx*2+dx;
|
|
94
|
+
if(field.v[p]<=0)continue;
|
|
95
|
+
const c=[rgb[p*3],rgb[p*3+1],rgb[p*3+2]].join(',');
|
|
96
|
+
counts.set(c,(counts.get(c)||0)+1);
|
|
97
|
+
}
|
|
98
|
+
const color=[...counts].sort((a,b)=>b[1]-a[1])[0]?.[0];
|
|
99
|
+
cell.rgb=color?color.split(',').map(Number):rgba.body;
|
|
100
|
+
}
|
|
101
|
+
return {ok:true,frame,cols,rows,icon:true,mood,source:'stage2/refinement/sentinel-a-tiny-shield-icon.svg',
|
|
102
|
+
bounds:showBounds(frame),visualOnly:true};
|
|
103
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel-specific "Warden V" face rig.
|
|
3
|
+
*
|
|
4
|
+
* Resting eyes continue to use the EXACT canonical Q eyeCells().
|
|
5
|
+
* During action, dot whites turn into rounded triangular shields whose TOP
|
|
6
|
+
* EDGES descend toward the bridge. No floating shocked / raised eyebrows.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately preserves qeyes' most important rule: an eye owns WHOLE
|
|
9
|
+
* terminal cells, including its dark pupil and empty eyelid cut-outs.
|
|
10
|
+
* Sentinel additions remain pure functions of time / geometry; no globals.
|
|
11
|
+
*/
|
|
12
|
+
import {eyeCells,EYE_WHITE} from '../../vendor/quox-dragon/qeyes.js';
|
|
13
|
+
|
|
14
|
+
const BR=[[1,8],[2,16],[4,32],[64,128]];
|
|
15
|
+
const clamp=(n,a=0,b=1)=>Math.max(a,Math.min(b,n));
|
|
16
|
+
|
|
17
|
+
export function renderWardenEyes(geo,variant,{
|
|
18
|
+
intensity=0, leftIntensity=null,rightIntensity=null,
|
|
19
|
+
gx=0,gy=.25,open=1, mono=false
|
|
20
|
+
}={}){
|
|
21
|
+
const {cols,rows,Hq}=geo;
|
|
22
|
+
const valid=Number.isFinite(Hq)&&Hq>0&&
|
|
23
|
+
Number.isSafeInteger(cols)&&Number.isSafeInteger(rows)&&cols>0&&rows>0;
|
|
24
|
+
if(!valid)throw new RangeError('Invalid Sentinel eye geometry.');
|
|
25
|
+
const severity=clamp(Number.isFinite(intensity)?intensity:0);
|
|
26
|
+
if(severity===0&&leftIntensity==null&&rightIntensity==null){
|
|
27
|
+
// The familiar resting Sentinel look is real Quox art, unchanged.
|
|
28
|
+
const original=eyeCells(geo,{open:clamp(open),gx,gy},variant);
|
|
29
|
+
return original.map(row=>row.map(cell=>cell?
|
|
30
|
+
{ch:cell.ch,rgb:mono?null:cell.rgb,bg:null}:null));
|
|
31
|
+
}
|
|
32
|
+
const W=cols*2,H=rows*4;
|
|
33
|
+
const axis=2*Math.round((geo.ox+.41*Hq)/2);
|
|
34
|
+
const sep=2*Math.ceil(variant.sep*Hq/2);
|
|
35
|
+
const R=variant.r*Hq,cy=(geo.oy??0)+variant.y*Hq;
|
|
36
|
+
const lit=new Uint8Array(W*H),foot=new Uint8Array(cols*rows);
|
|
37
|
+
let visible=0;
|
|
38
|
+
const eyes=[
|
|
39
|
+
{side:-1,cx:axis-sep,level:clamp(leftIntensity??severity)},
|
|
40
|
+
{side:1,cx:axis+sep,level:clamp(rightIntensity??severity)}
|
|
41
|
+
];
|
|
42
|
+
for(const {side,cx,level} of eyes){
|
|
43
|
+
const rx=R*variant.ox,ry=R*variant.oy;
|
|
44
|
+
const lean=level*0.72; // angled down *toward the bridge*
|
|
45
|
+
const lo=Math.floor(cx-rx-2),hi=Math.ceil(cx+rx+2);
|
|
46
|
+
const ty=Math.max(0,Math.floor(cy-ry-2)),by=Math.min(H-1,Math.ceil(cy+ry+2));
|
|
47
|
+
const pupilRadius=Math.max(1.1,R*variant.pupil*(1-level*.24));
|
|
48
|
+
const pupilX=cx+clamp(gx,-1,1)*Math.max(0,rx-pupilRadius-1)*.48;
|
|
49
|
+
const pupilY=cy+clamp(gy,-1,1)*Math.max(0,ry-pupilRadius-1)*.48+level*ry*.18;
|
|
50
|
+
for(let y=ty;y<=by;y++)for(let x=Math.max(0,lo);x<=Math.min(W-1,hi);x++){
|
|
51
|
+
const u=(x+.5-cx)/rx;
|
|
52
|
+
if(Math.abs(u)>1)continue;
|
|
53
|
+
const v=Math.sqrt(Math.max(0,1-u*u));
|
|
54
|
+
const inner=-side*u; // LEFT inner edge is right, RIGHT inner edge is left
|
|
55
|
+
const top=cy-ry*v+lean*ry*(.18+.42*clamp((inner+1)/2));
|
|
56
|
+
const bottom=cy+ry*v*(1-level*.14);
|
|
57
|
+
const yy=y+.5;
|
|
58
|
+
const inRoundEye=yy>=top-1.2&&yy<=bottom+1.1;
|
|
59
|
+
if(inRoundEye)foot[Math.floor(y/4)*cols+Math.floor(x/2)]=1;
|
|
60
|
+
if(yy<top||yy>bottom||clamp(open)<.12)continue;
|
|
61
|
+
// Keep an upper bright ridge, even for the severest glare.
|
|
62
|
+
const ridge=yy-top<1.15;
|
|
63
|
+
const dist=Math.hypot((x+.5-pupilX), (yy-pupilY));
|
|
64
|
+
const pupil=!ridge&&dist<pupilRadius;
|
|
65
|
+
const shine=pupil&&
|
|
66
|
+
Math.abs(x+.5-(pupilX-pupilRadius*.30))<.9&&
|
|
67
|
+
Math.abs(yy-(pupilY-pupilRadius*.40))<.9&&
|
|
68
|
+
pupilRadius>2.7;
|
|
69
|
+
if(!pupil||shine){
|
|
70
|
+
lit[y*W+x]=1;visible++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Cell-level margin: no solid blue body allowed to flood an eye's edge.
|
|
74
|
+
// The body is allowed around the two eyes but never inside either one.
|
|
75
|
+
for(let r=Math.max(0,Math.floor((cy-ry-2)/4));r<=Math.min(rows-1,Math.ceil((cy+ry+2)/4));r++)
|
|
76
|
+
for(let c=Math.max(0,Math.floor((cx-rx-2)/2));c<=Math.min(cols-1,Math.ceil((cx+rx+2)/2));c++){
|
|
77
|
+
const x=c*2+1,y=r*4+2;
|
|
78
|
+
if(((x-cx)/(rx+1.0))**2+((y-cy)/(ry+1.0))**2<=1)
|
|
79
|
+
foot[r*cols+c]=1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const output=[];
|
|
83
|
+
for(let row=0;row<rows;row++){
|
|
84
|
+
const cells=[];
|
|
85
|
+
for(let col=0;col<cols;col++){
|
|
86
|
+
let bits=0;
|
|
87
|
+
for(let dy=0;dy<4;dy++)for(let dx=0;dx<2;dx++)
|
|
88
|
+
if(lit[(row*4+dy)*W+col*2+dx])bits|=BR[dy][dx];
|
|
89
|
+
cells.push(bits?{ch:String.fromCodePoint(0x2800+bits),rgb:mono?null:EYE_WHITE,bg:null}:
|
|
90
|
+
foot[row*cols+col]?{ch:' ',rgb:null,bg:null}:null);
|
|
91
|
+
}
|
|
92
|
+
output.push(cells);
|
|
93
|
+
}
|
|
94
|
+
return output;
|
|
95
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny, deterministic absolute SVG path parser and 2x2-coverage shape helper.
|
|
3
|
+
* Only M/L/H/V/C/Q/Z are accepted; unsupported commands fail closed.
|
|
4
|
+
* No DOM, browser APIs, canvas dependency, third-party SVG parser or I/O.
|
|
5
|
+
*
|
|
6
|
+
* Unlike naive point-stamping, all fills are tested at sub-dot offsets identical
|
|
7
|
+
* to Quox's 2x2 vector->Braille rasterisation in qart.js and qdragon.js.
|
|
8
|
+
*/
|
|
9
|
+
const TOKEN=/[MLHVCQZ]|[-+]?(?:\d*\.\d+|\d+\.?)(?:e[-+]?\d+)?/g;
|
|
10
|
+
const CMD=/^[MLHVCQZ]$/;
|
|
11
|
+
const PTS=[0.25,0.75];
|
|
12
|
+
|
|
13
|
+
export function flattenPath(source){
|
|
14
|
+
if(typeof source!=='string'||source.length>24000)
|
|
15
|
+
throw new TypeError('A path must be a short SVG path string.');
|
|
16
|
+
const tokens=source.match(TOKEN)||[];
|
|
17
|
+
// Reject SVG commands we have not deliberately implemented, including
|
|
18
|
+
// arc and relative-path syntax. Silently skipping them corrupts the art.
|
|
19
|
+
if(source.replace(TOKEN,'').replace(/[\s,]/g,'')!=='')
|
|
20
|
+
throw new RangeError('Unsupported SVG path command or syntax.');
|
|
21
|
+
if(!tokens.length||tokens[0]!=='M')throw new RangeError('Path must start with absolute M.');
|
|
22
|
+
const polygons=[];let poly=[],point=[0,0],start=[0,0],i=0,cmd=null,loops=0;
|
|
23
|
+
const take=()=>{
|
|
24
|
+
const v=tokens[i++];
|
|
25
|
+
if(v===undefined||CMD.test(v))throw new RangeError('Invalid SVG numeric argument.');
|
|
26
|
+
const n=Number(v);if(!Number.isFinite(n))throw new RangeError('Invalid SVG numeric coordinate.');
|
|
27
|
+
return n;
|
|
28
|
+
};
|
|
29
|
+
const push=(p)=>{point=p;poly.push(p);};
|
|
30
|
+
while(i<tokens.length){
|
|
31
|
+
if(++loops>20000)throw new RangeError('SVG path command limit reached.');
|
|
32
|
+
if(CMD.test(tokens[i]))cmd=tokens[i++];
|
|
33
|
+
if(!cmd)throw new RangeError('Unexpected coordinate before command.');
|
|
34
|
+
if(cmd==='M'){
|
|
35
|
+
if(poly.length>2)polygons.push(poly);
|
|
36
|
+
poly=[];start=[take(),take()];push(start);cmd='L';continue;
|
|
37
|
+
}
|
|
38
|
+
if(cmd==='L'){push([take(),take()]);continue;}
|
|
39
|
+
if(cmd==='H'){push([take(),point[1]]);continue;}
|
|
40
|
+
if(cmd==='V'){push([point[0],take()]);continue;}
|
|
41
|
+
if(cmd==='Q'){
|
|
42
|
+
const a=point,b=[take(),take()],c=[take(),take()];
|
|
43
|
+
const n=24;
|
|
44
|
+
for(let j=1;j<=n;j++){
|
|
45
|
+
const u=j/n,v=1-u;
|
|
46
|
+
push([v*v*a[0]+2*v*u*b[0]+u*u*c[0],
|
|
47
|
+
v*v*a[1]+2*v*u*b[1]+u*u*c[1]]);
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if(cmd==='C'){
|
|
52
|
+
const a=point,b=[take(),take()],c=[take(),take()],d=[take(),take()];
|
|
53
|
+
const n=36;
|
|
54
|
+
for(let j=1;j<=n;j++){
|
|
55
|
+
const u=j/n,v=1-u;
|
|
56
|
+
push([v*v*v*a[0]+3*v*v*u*b[0]+3*v*u*u*c[0]+u*u*u*d[0],
|
|
57
|
+
v*v*v*a[1]+3*v*v*u*b[1]+3*v*u*u*c[1]+u*u*u*d[1]]);
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if(cmd==='Z'){
|
|
62
|
+
if(poly.length>=3)polygons.push(poly);
|
|
63
|
+
poly=[];point=start;cmd=null;continue;
|
|
64
|
+
}
|
|
65
|
+
throw new RangeError('SVG command not supported: '+cmd);
|
|
66
|
+
}
|
|
67
|
+
if(poly.length>=2)polygons.push(poly);
|
|
68
|
+
if(!polygons.length)throw new RangeError('SVG path has no drawable coordinates.');
|
|
69
|
+
return polygons;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function inside(polygons,x,y){
|
|
73
|
+
let yes=false;
|
|
74
|
+
for(const poly of polygons){
|
|
75
|
+
for(let i=0,j=poly.length-1;i<poly.length;j=i++){
|
|
76
|
+
const [ax,ay]=poly[i],[bx,by]=poly[j];
|
|
77
|
+
if((ay>y)!==(by>y)&&x<(bx-ax)*(y-ay)/(by-ay)+ax)yes=!yes;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return yes;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function segmentDistSq(px,py,ax,ay,bx,by){
|
|
84
|
+
const dx=bx-ax,dy=by-ay,dd=dx*dx+dy*dy;
|
|
85
|
+
const t=dd?Math.max(0,Math.min(1,((px-ax)*dx+(py-ay)*dy)/dd)):0;
|
|
86
|
+
const a=px-(ax+dx*t),b=py-(ay+dy*t);
|
|
87
|
+
return a*a+b*b;
|
|
88
|
+
}
|
|
89
|
+
export function nearStroke(polygons,x,y,width){
|
|
90
|
+
const radius=width*.5,limit=radius*radius;
|
|
91
|
+
for(const poly of polygons)
|
|
92
|
+
for(let i=1;i<poly.length;i++)
|
|
93
|
+
if(segmentDistSq(x,y,...poly[i-1],...poly[i])<=limit)return true;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Callback receives target dot x/y only if >=2 of the same four supersamples
|
|
99
|
+
* that the production Q uses lie within the source-space predicate.
|
|
100
|
+
*/
|
|
101
|
+
export function sampleShape(polygons,{W,H,scale,ox,oy,stroke=0},put){
|
|
102
|
+
const points=polygons.flat();
|
|
103
|
+
const xx=points.map(p=>p[0]),yy=points.map(p=>p[1]);
|
|
104
|
+
const guard=stroke?stroke*.5+1:1;
|
|
105
|
+
const x0=Math.max(1,Math.floor(ox+(Math.min(...xx)-guard)*scale)-1);
|
|
106
|
+
const x1=Math.min(W-2,Math.ceil(ox+(Math.max(...xx)+guard)*scale)+1);
|
|
107
|
+
const y0=Math.max(1,Math.floor(oy+(Math.min(...yy)-guard)*scale)-1);
|
|
108
|
+
const y1=Math.min(H-2,Math.ceil(oy+(Math.max(...yy)+guard)*scale)+1);
|
|
109
|
+
if(stroke){
|
|
110
|
+
// Short brows and mouth outlines still use the straightforward
|
|
111
|
+
// segment-distance test, which is exact for these narrow contours.
|
|
112
|
+
for(let y=y0;y<=y1;y++)for(let x=x0;x<=x1;x++){
|
|
113
|
+
let n=0;
|
|
114
|
+
for(const sy of PTS)for(const sx of PTS)
|
|
115
|
+
if(nearStroke(polygons,(x+sx-ox)/scale,(y+sy-oy)/scale,stroke))n++;
|
|
116
|
+
if(n>=2)put(x,y);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Scanline fill: EXACTLY the same even/odd polygon rule and four sample
|
|
121
|
+
// locations, but calculate intersections once per sub-dot row instead of
|
|
122
|
+
// walking hundreds of SVG edges for every single dot. This dramatically
|
|
123
|
+
// reduces cold-frame latency while preserving the original Q 2x2 coverage.
|
|
124
|
+
const width=x1-x0+1,counts=new Uint8Array(width*(y1-y0+1));
|
|
125
|
+
const edges=[];
|
|
126
|
+
for(const poly of polygons)for(let i=0,j=poly.length-1;i<poly.length;j=i++){
|
|
127
|
+
const [ax,ay]=poly[j],[bx,by]=poly[i];
|
|
128
|
+
if(ay===by)continue;
|
|
129
|
+
edges.push([ax,ay,(bx-ax)/(by-ay),Math.min(ay,by),Math.max(ay,by)]);
|
|
130
|
+
}
|
|
131
|
+
for(let y=y0;y<=y1;y++)for(const sy of PTS){
|
|
132
|
+
const ys=(y+sy-oy)/scale;
|
|
133
|
+
const crosses=[];
|
|
134
|
+
for(const [ax,ay,slope,lo,hi] of edges)
|
|
135
|
+
if(ys>=lo&&ys<hi)crosses.push(ox+(ax+(ys-ay)*slope)*scale);
|
|
136
|
+
crosses.sort((a,b)=>a-b);
|
|
137
|
+
for(let i=0;i+1<crosses.length;i+=2){
|
|
138
|
+
const a=crosses[i],b=crosses[i+1];
|
|
139
|
+
for(const sx of PTS){
|
|
140
|
+
const start=Math.max(x0,Math.ceil(a-sx));
|
|
141
|
+
const end=Math.min(x1,Math.ceil(b-sx)-1);
|
|
142
|
+
for(let x=start;x<=end;x++)counts[(y-y0)*width+x-x0]++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for(let y=y0;y<=y1;y++)for(let x=x0;x<=x1;x++)
|
|
147
|
+
if(counts[(y-y0)*width+x-x0]>=2)put(x,y);
|
|
148
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Shared authored SVG → original Q cells, supporting independently owned eyes. */
|
|
2
|
+
import {flattenPath,sampleShape} from '../design/stage3/svg-coverage.js';
|
|
3
|
+
import {makeField,fieldToCells} from '../vendor/quox-dragon/qart.js';
|
|
4
|
+
const attr=(s,k)=>s.match(new RegExp(`(?:^|\\s)${k}="([^"]*)"`))?.[1];
|
|
5
|
+
export function parseSvgRig(svg){
|
|
6
|
+
if(!svg.includes('viewBox="0 0 800 480"'))throw Error('Expected authored 800x480 master');
|
|
7
|
+
const ids=new Set(),groups=new Map(),stack=[];
|
|
8
|
+
for(const [tag]of svg.matchAll(/<g\s[^>]*>|<\/g>|<path\s[^>]*\/>/g)){
|
|
9
|
+
if(tag.startsWith('<g '))stack.push(attr(tag,'id'));else if(tag==='</g>')stack.pop();else groups.set(attr(tag,'id'),stack.at(-1));
|
|
10
|
+
}
|
|
11
|
+
const paths=[...svg.matchAll(/<path\s[^>]*\/>/g)].map(([tag])=>{
|
|
12
|
+
const id=attr(tag,'id'),d=attr(tag,'d'),stroke=attr(tag,'stroke'),fill=attr(tag,'fill');
|
|
13
|
+
if(!id||ids.has(id)||!d)throw Error('Every path requires unique id and geometry');ids.add(id);
|
|
14
|
+
const colour=stroke||fill;if(!/^#[0-9a-f]{6}$/i.test(colour))throw Error('Explicit hex colour required');
|
|
15
|
+
const width=stroke?Number(attr(tag,'stroke-width')):0;
|
|
16
|
+
if(!Number.isFinite(width)||width<0||width>30)throw Error('Invalid stroke width');
|
|
17
|
+
return {id,group:groups.get(id),polygons:flattenPath(d),rgb:[1,3,5].map(i=>parseInt(colour.slice(i,i+2),16)),stroke:width,erase:colour==='#080d19'};
|
|
18
|
+
});
|
|
19
|
+
const eyes=paths.filter(p=>p.id.endsWith('-eye-socket')).map(p=>{
|
|
20
|
+
const name=p.id.slice(0,-11),white=paths.find(p=>p.id===name+'-eye-white'),pupil=paths.find(p=>p.id===name+'-pupil');
|
|
21
|
+
if(!white||!pupil)throw Error('Every eye needs white and pupil');return {name,socket:p,white,pupil};
|
|
22
|
+
});
|
|
23
|
+
if(!paths.length||!eyes.length)throw Error('Missing anatomy or eyes');
|
|
24
|
+
const points=paths.flatMap(p=>p.polygons.flat());
|
|
25
|
+
const x=points.map(p=>p[0]),y=points.map(p=>p[1]);
|
|
26
|
+
return {paths,eyes,bounds:{left:Math.min(...x),top:Math.min(...y),right:Math.max(...x),bottom:Math.max(...y)}};
|
|
27
|
+
}
|
|
28
|
+
export function renderSvgRig(rig,{cols=39,rows=11,mono=false}={}){
|
|
29
|
+
if(!Number.isSafeInteger(cols)||!Number.isSafeInteger(rows)||cols<28||rows<10||cols>200||rows>72)throw RangeError('Art needs 28–200 columns and 10–72 rows');
|
|
30
|
+
if(typeof mono!=='boolean')throw TypeError('mono must be boolean');
|
|
31
|
+
const W=cols*2,H=rows*4,{left,top,right,bottom}=rig.bounds;
|
|
32
|
+
const scale=Math.min((W-4)/(right-left),(H-4)/(bottom-top));
|
|
33
|
+
const geometry={W,H,scale,ox:(W-(right-left)*scale)/2-left*scale,oy:(H-(bottom-top)*scale)/2-top*scale};
|
|
34
|
+
const field=makeField(W,H),labels=new Array(W*H).fill(''),colour=new Array(W*H).fill(null);
|
|
35
|
+
const coverage=p=>{const indices=[];sampleShape(p.polygons,{...geometry,stroke:p.stroke},(x,y)=>indices.push(y*W+x));return indices;};
|
|
36
|
+
const eyeIds=new Set(rig.eyes.flatMap(e=>[e.socket.id,e.white.id,e.pupil.id]));
|
|
37
|
+
function paint(p){for(const i of coverage(p)){field.v[i]=p.erase?0:1;labels[i]=p.id;colour[i]=p.rgb;}}
|
|
38
|
+
for(const p of rig.paths)if(!eyeIds.has(p.id))paint(p);
|
|
39
|
+
// Clear all eye-owned cells FIRST, so the far eye cannot erase the near eye.
|
|
40
|
+
const owned=new Set();
|
|
41
|
+
for(const e of rig.eyes)for(const i of coverage(e.socket))owned.add(Math.floor(i%W/2)+Math.floor(Math.floor(i/W)/4)*cols);
|
|
42
|
+
for(const cell of owned){const x=cell%cols*2,y=Math.floor(cell/cols)*4;for(let dy=0;dy<4;dy++)for(let dx=0;dx<2;dx++){const i=(y+dy)*W+x+dx;field.v[i]=0;labels[i]='';colour[i]=null;}}
|
|
43
|
+
const pupilDots={};
|
|
44
|
+
for(const e of rig.eyes){
|
|
45
|
+
paint(e.white);let dots=coverage(e.pupil);
|
|
46
|
+
if(!dots.length){
|
|
47
|
+
// Optical correction is local to THIS eye; no Lucid-specific coordinates.
|
|
48
|
+
const points=e.pupil.polygons.flat(),cx=points.reduce((n,p)=>n+p[0],0)/points.length,cy=points.reduce((n,p)=>n+p[1],0)/points.length;
|
|
49
|
+
let best=-1,d=Infinity;
|
|
50
|
+
for(let i=0;i<labels.length;i++)if(labels[i]===e.white.id&&field.v[i]){const q=(i%W+.5-(geometry.ox+cx*scale))**2+(Math.floor(i/W)+.5-(geometry.oy+cy*scale))**2;if(q<d){best=i;d=q;}}
|
|
51
|
+
if(best>=0)dots=[best];
|
|
52
|
+
}
|
|
53
|
+
for(const i of dots){field.v[i]=0;labels[i]=e.pupil.id;colour[i]=null;}
|
|
54
|
+
pupilDots[e.name]=dots.length;
|
|
55
|
+
}
|
|
56
|
+
const frame=fieldToCells(field,{color:false,quant:false});let ink=0;
|
|
57
|
+
for(let y=0;y<rows;y++)for(let x=0;x<cols;x++){
|
|
58
|
+
const c=frame[y][x];c.bg=null;c.rgb=null;let chosen=-1,rank=-1;
|
|
59
|
+
for(let dy=0;dy<4;dy++)for(let dx=0;dx<2;dx++){const i=(y*4+dy)*W+x*2+dx;if(!field.v[i])continue;const r=labels[i].endsWith('-eye-white')?2:1;if(r>rank){rank=r;chosen=i;}}
|
|
60
|
+
if(chosen>=0){ink++;if(!mono)c.rgb=[...colour[chosen]];}
|
|
61
|
+
}
|
|
62
|
+
return {ok:true,frame,geometry,metrics:{cols,rows,ink,pupilDots,eyeOwnedCells:owned.size},visualOnly:true,demo:true};
|
|
63
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {readFile} from 'node:fs/promises';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
3
|
+
import {activityView as baseView} from '../hyperion-v3/activity.mjs';
|
|
4
|
+
|
|
5
|
+
export const CHARACTERS=Object.freeze({
|
|
6
|
+
lucid:{name:'Lucid',version:'recovery V5',review:'../../design/lucid/recovery/review.mjs',power:'sweep',powerName:'Horizon Sweep',powerDuration:3200,arrival:{scene:'attach',duration:3200}},
|
|
7
|
+
hyperion:{name:'Hyperion',version:'V2 acting',review:'../../design/hyperion/v2/review.mjs',power:'cluster',powerName:'Cluster Forge',powerDuration:4800,arrival:{scene:'attach',duration:3400}},
|
|
8
|
+
seer:{name:'Seer',version:'V4 flowing eight',review:'../../design/seer/v4/review.mjs',power:'pattern',powerName:'Pattern Sight',powerDuration:5200,arrival:{scene:'swim',duration:5800}},
|
|
9
|
+
sentinel:{name:'Sentinel',version:'A solid (provisional)',review:'../../design/stage3/companion-review.mjs',power:'scorch',powerName:'Aegis Inferno',powerDuration:3800,arrival:{scene:'greeting',duration:2400}},
|
|
10
|
+
cipher:{name:'Cipher',version:'V2 network acting',review:'../../design/cipher/v2/review.mjs',power:'trace',powerName:'Trace Route',powerDuration:4800,arrival:{scene:'attach',duration:3200}},
|
|
11
|
+
drago:{name:'Drago',version:'V4 scoped inferno',review:'../../design/drago/v4/review.mjs',power:'fire',powerName:'Scoped Inferno',powerDuration:6800,arrival:{scene:'attach',duration:3800}},
|
|
12
|
+
});
|
|
13
|
+
const ids=Object.keys(CHARACTERS);
|
|
14
|
+
// Host scene -> art clip. Sentinel never shows 'all-clear' (needs verified evidence); Drago has no 'proud'/'curious'/'focus' clips.
|
|
15
|
+
const SCENES=Object.freeze({
|
|
16
|
+
sentinel:{curious:'scan',focus:'scan',concerned:'failure',proud:'idle'},
|
|
17
|
+
// Cipher claims no network facts: 'restored'/'delay'/'broken' routes are simulated, so completion stays on plain listening.
|
|
18
|
+
cipher:{curious:'inspect',focus:'inspect',standby:'idle',concerned:'broken',proud:'idle'},
|
|
19
|
+
drago:{curious:'inspect',focus:'active',concerned:'concerned',proud:'acknowledge'},
|
|
20
|
+
});
|
|
21
|
+
const SENTINEL_MOOD=Object.freeze({idle:'cute',greeting:'cute',standby:'cute',scan:'alert',approval:'alert',failure:'alert',warden:'warden',brace:'warden',scorch:'warden'});
|
|
22
|
+
export function character(id){
|
|
23
|
+
if(!Object.hasOwn(CHARACTERS,id))throw new Error(`Choose ${ids.slice(0,-1).join(', ')} or ${ids.at(-1)}.`);
|
|
24
|
+
return CHARACTERS[id];
|
|
25
|
+
}
|
|
26
|
+
export function modulePath(id){character(id);return fileURLToPath(new URL(`./${id}.mjs`,import.meta.url));}
|
|
27
|
+
export function reviewPath(id){return fileURLToPath(new URL(character(id).review,import.meta.url));}
|
|
28
|
+
/** Brief compact entrance on attach. Host facts that need attention (approval, issue) always pre-empt it. */
|
|
29
|
+
export function arrivalView(id,state,now){
|
|
30
|
+
const {arrival}=character(id),elapsed=now-state.started;
|
|
31
|
+
if(!arrival||!(elapsed>=0)||elapsed>=arrival.duration)return null;
|
|
32
|
+
if(state.approvals.length||state.issue||state.terminal)return null;
|
|
33
|
+
return {scene:arrival.scene,time:elapsed,motionTime:elapsed,caption:state.phase==='connecting'?'Connecting…':'Following the conversation.'};
|
|
34
|
+
}
|
|
35
|
+
export function activityView(id,state,now,{arrival=false}={}){
|
|
36
|
+
const spec=character(id);
|
|
37
|
+
const entrance=arrival?arrivalView(id,state,now):null;
|
|
38
|
+
if(entrance)return entrance;
|
|
39
|
+
const v=baseView(state,now);
|
|
40
|
+
if(v.scene==='cluster')return {...v,scene:spec.power,time:v.time*spec.powerDuration/4800,caption:`Working · ${spec.powerName} visual`};
|
|
41
|
+
if(id==='lucid')return {...v,scene:({concerned:'uncertain',proud:'delighted'})[v.scene]??v.scene};
|
|
42
|
+
if(SCENES[id])return {...v,scene:(id==='sentinel'&&state.approvals.length?'approval':SCENES[id][v.scene])??v.scene};
|
|
43
|
+
return v;
|
|
44
|
+
}
|
|
45
|
+
export async function loadCharacter(id){
|
|
46
|
+
const spec=character(id);
|
|
47
|
+
if(id==='lucid'){
|
|
48
|
+
const [{parseMaster},{renderLucidCharacter}]=await Promise.all([
|
|
49
|
+
import('../../design/lucid/recovery/render.mjs'),import('../../design/lucid/recovery/character.mjs'),
|
|
50
|
+
]);
|
|
51
|
+
const rig=parseMaster(await readFile(new URL('../../design/lucid/recovery/lucid-master.svg',import.meta.url),'utf8'));
|
|
52
|
+
return {...spec,render:(scene,time,options)=>renderLucidCharacter(rig,scene,time,options)};
|
|
53
|
+
}
|
|
54
|
+
if(id==='sentinel'){
|
|
55
|
+
const [{renderSentinelCharacter},{renderSentinelIcon}]=await Promise.all([
|
|
56
|
+
import('../../design/stage3/sentinel-character.js'),import('../../design/stage3/sentinel-icon.js'),
|
|
57
|
+
]);
|
|
58
|
+
return {...spec,render:(scene,time,{cols,rows,mono=false,still=false}={})=>{
|
|
59
|
+
// The authored character refuses below 48x16 cells (shrinking it was never approved). Smaller slots get the approved shield icon.
|
|
60
|
+
if(cols<48||rows<16){
|
|
61
|
+
const h=Math.max(6,Math.min(rows,10)),w=Math.max(12,Math.min(cols,h*2));
|
|
62
|
+
return renderSentinelIcon({cols:w,rows:h,mono,mood:SENTINEL_MOOD[scene]??'cute'});
|
|
63
|
+
}
|
|
64
|
+
// Scan is not a loop; bounce it so a long job keeps sweeping without a restart pop.
|
|
65
|
+
const t=scene==='scan'&&!still?(u=>u<2500?u:5000-u)(time%5000):time;
|
|
66
|
+
return renderSentinelCharacter(scene,t,{cols,rows,mono,still});
|
|
67
|
+
}};
|
|
68
|
+
}
|
|
69
|
+
const {parseSvgRig}=await import('../../factory/svg-rig.mjs');
|
|
70
|
+
if(id==='cipher'){
|
|
71
|
+
const [rig,{renderCipher}]=await Promise.all([
|
|
72
|
+
readFile(new URL('../../design/cipher/v1/cipher-master.svg',import.meta.url),'utf8').then(parseSvgRig),
|
|
73
|
+
import('../../design/cipher/v2/character.mjs'),
|
|
74
|
+
]);
|
|
75
|
+
return {...spec,render:(scene,time,options)=>renderCipher(rig,scene,time,options)};
|
|
76
|
+
}
|
|
77
|
+
if(id==='drago'){
|
|
78
|
+
const [rig,{renderDrago}]=await Promise.all([
|
|
79
|
+
readFile(new URL('../../design/drago/v1/drago-master.svg',import.meta.url),'utf8').then(parseSvgRig),
|
|
80
|
+
import('../../design/drago/v4/character.mjs'),
|
|
81
|
+
]);
|
|
82
|
+
return {...spec,render:(scene,time,options)=>renderDrago(rig,scene,time,options)};
|
|
83
|
+
}
|
|
84
|
+
const source=id==='seer'?'../../design/seer/v1/seer-master.svg':'../../design/hyperion/v1/hyperion-master.svg';
|
|
85
|
+
const rig=parseSvgRig(await readFile(new URL(source,import.meta.url),'utf8'));
|
|
86
|
+
const render=id==='seer'?(await import('../../design/seer/v4/character.mjs')).renderSeer:(await import('../../design/hyperion/v2/character.mjs')).renderHyperion;
|
|
87
|
+
return {...spec,render:(scene,time,options)=>render(rig,scene,time,options)};
|
|
88
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {performance} from 'node:perf_hooks';
|
|
2
|
+
import {composeMessage} from '../../design/lucid/recovery/layout.mjs';
|
|
3
|
+
import {character,CHARACTERS} from './characters.mjs';
|
|
4
|
+
const ENTRANCE_MS=Object.fromEntries(Object.entries(CHARACTERS).map(([k,v])=>[k,v.arrival.duration]));
|
|
5
|
+
import {loadDragonView} from './view.mjs';
|
|
6
|
+
import {initialActivity,activityEvent} from '../hyperion-v3/activity.mjs';
|
|
7
|
+
|
|
8
|
+
/** Shared display lifecycle. Host events choose expressions; art makes no decisions. */
|
|
9
|
+
export async function createDragonCompanion(id,{output=process.stdout,now=()=>performance.now(),still=false,arrival=true,mono=false,persist=true}={}){
|
|
10
|
+
character(id);
|
|
11
|
+
// Piped output does not need to load or render any art.
|
|
12
|
+
const actor=output.isTTY?await loadDragonView(id,{still,arrival,mono}):null;
|
|
13
|
+
let state=initialActivity(now()),height=0,boundary=true,closed=false,disabled=false,timer,closing;
|
|
14
|
+
const erase=()=>{
|
|
15
|
+
if(!height)return;
|
|
16
|
+
output.write(`\x1b[${height}A\r`);
|
|
17
|
+
for(let i=0;i<height;i++)output.write('\x1b[2K\n');
|
|
18
|
+
output.write(`\x1b[${height}A\r`);height=0;
|
|
19
|
+
};
|
|
20
|
+
const draw=()=>{
|
|
21
|
+
if(closed||disabled||!output.isTTY||!boundary)return;
|
|
22
|
+
const cols=output.columns||80,rows=output.rows||24;
|
|
23
|
+
if(rows<5||cols<16)return;
|
|
24
|
+
const started=state.started;
|
|
25
|
+
// greeting-only: after the entrance the block is withdrawn for good (host text is untouched).
|
|
26
|
+
if(!persist&&!still&&now()-started>=ENTRANCE_MS[id]&&!state.approvals.length){erase();disabled=true;clearInterval(timer);return;}
|
|
27
|
+
const v=actor.view(state,now(),{cols,rows});
|
|
28
|
+
const lines=composeMessage(v.frame,actor.name.toUpperCase()+'\n'+v.caption,{terminalCols:cols});
|
|
29
|
+
if(lines.length>=rows)return;
|
|
30
|
+
erase();output.write(lines.join('\n')+'\n');height=lines.length;
|
|
31
|
+
};
|
|
32
|
+
const safeDraw=()=>{try{draw();}catch{disabled=true;clearInterval(timer);try{erase();}catch{}}};
|
|
33
|
+
// Reflow invalidates old cursor coordinates. Do not erase after resize.
|
|
34
|
+
const resize=()=>{height=0;disabled=true;clearInterval(timer);};
|
|
35
|
+
output.on?.('resize',resize);
|
|
36
|
+
safeDraw();
|
|
37
|
+
if(output.isTTY&&!disabled){timer=setInterval(safeDraw,1000/6);timer.unref?.();}
|
|
38
|
+
return {
|
|
39
|
+
event(event){if(!closed&&!closing){state=activityEvent(state,event,now());safeDraw();}},
|
|
40
|
+
write(text){
|
|
41
|
+
if(closed)return;
|
|
42
|
+
erase();output.write(text);
|
|
43
|
+
if(text.length)boundary=text.endsWith('\n');
|
|
44
|
+
if(boundary)safeDraw();
|
|
45
|
+
},
|
|
46
|
+
suspend(){erase();disabled=true;clearInterval(timer);},
|
|
47
|
+
close(){
|
|
48
|
+
if(closing)return closing;
|
|
49
|
+
// Set closing before starting async work so concurrent closes share cleanup.
|
|
50
|
+
closing=Promise.resolve().then(async()=>{
|
|
51
|
+
state=activityEvent(state,{type:'stream.closed'},now());
|
|
52
|
+
try{
|
|
53
|
+
if(output.isTTY&&!disabled&&!still&&state.phase==='complete'&&!state.issue&&!state.approvals.length){
|
|
54
|
+
const remaining=Math.max(0,2600-(now()-state.changed));
|
|
55
|
+
if(remaining)await new Promise(resolve=>setTimeout(resolve,remaining));
|
|
56
|
+
}
|
|
57
|
+
safeDraw();
|
|
58
|
+
}finally{closed=true;clearInterval(timer);output.off?.('resize',resize);}
|
|
59
|
+
});
|
|
60
|
+
return closing;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {artSlot} from '../../design/lucid/recovery/layout.mjs';
|
|
2
|
+
import {initialActivity,activityEvent} from '../hyperion-v3/activity.mjs';
|
|
3
|
+
import {character,loadCharacter,activityView} from './characters.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pure per-frame view for hosts that own their own screen (Quoxon's Ink, the CLI block).
|
|
7
|
+
* Same reducer, same renderer, same priorities as the line-based companion; no timers, no terminal control.
|
|
8
|
+
* view() returns {frame,caption,slot,name,scene}; frame is null when the terminal is too small for art.
|
|
9
|
+
*/
|
|
10
|
+
export async function loadDragonView(id,{still=false,arrival=true,mono=false,persist=true}={}){
|
|
11
|
+
character(id);
|
|
12
|
+
const actor=await loadCharacter(id);
|
|
13
|
+
return {
|
|
14
|
+
id,name:actor.name,arrivalMs:character(id).arrival.duration,
|
|
15
|
+
initial:initialActivity,
|
|
16
|
+
reduce:activityEvent,
|
|
17
|
+
view(state,now,{cols=80,rows=24}={}){
|
|
18
|
+
// Greeting-only: withdrawn for good once the entrance is over, unless an approval is pending.
|
|
19
|
+
if(!persist&&!still&&now-state.started>=character(id).arrival.duration&&!state.approvals.length)return {frame:null,caption:null,hidden:true,name:actor.name,scene:null};
|
|
20
|
+
const slot=artSlot(cols,Math.max(1,rows));
|
|
21
|
+
if(id==='seer'||id==='cipher'){slot.cols=Math.min(39,slot.cols);slot.rows=Math.min(11,slot.rows);}
|
|
22
|
+
const v=activityView(id,state,now,{arrival:arrival&&!still});
|
|
23
|
+
const result=slot.showArt?actor.render(v.scene,v.time,{...slot,still,mono,motionTime:v.motionTime}):null;
|
|
24
|
+
if(result&&!result.ok)throw new Error(result.code);
|
|
25
|
+
return {frame:result?.frame??null,caption:v.caption,slot,name:actor.name,scene:v.scene};
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|