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,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The horned Q as Braille art, and every motion the owner reviewed in the lab (~/quoxon-work/intro/lab):
|
|
3
|
+
* Orbit & Lock (the intro), Portal, Particle collapse. CLI-QUOX.
|
|
4
|
+
*
|
|
5
|
+
* PURE AND PORTABLE on purpose: no Ink, no process, no timers, no terminal APIs. Inputs are size, progress
|
|
6
|
+
* (or ms), state and seed; the core output is rows of cells `{ ch, rgb, bg? }` (rgb null = default colour; bg, when
|
|
7
|
+
* present, a background colour: the dragon's eyes in qdragon.js use it). The
|
|
8
|
+
* same module renders in the terminal (cellsToAnsi) and in a browser (web/qface.js), which is how the Q
|
|
9
|
+
* becomes the face of the app on cli.quox.ai as well as in the CLI.
|
|
10
|
+
*
|
|
11
|
+
* The Q is rasterised from a small vector description (ring bowl, tapered tail, two curved horns) at the
|
|
12
|
+
* dot resolution of the target size, so every size has a smooth bowl instead of a scaled bitmap. Each dot
|
|
13
|
+
* knows which part it belongs to (bowl, tail, horn) and how far along that part it sits, which is what the
|
|
14
|
+
* tail sweep, the horn glint and the portal reveal key off.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const SKY = [56, 189, 248];
|
|
18
|
+
export const VIOLET = [168, 85, 247];
|
|
19
|
+
export const AMBER = [245, 158, 11];
|
|
20
|
+
export const RED = [239, 68, 68];
|
|
21
|
+
const TAU = Math.PI * 2;
|
|
22
|
+
const ASPECT = 0.82; // silhouette width / height, in square dots
|
|
23
|
+
|
|
24
|
+
// size -> dot height. A Braille cell is 2 dots wide and 4 tall, so rows = H / 4.
|
|
25
|
+
// small is the lab's compact particle Q (under 60 cols); large/mid are the intro sizes.
|
|
26
|
+
export const SIZES = { large: 72, mid: 44, small: 24 };
|
|
27
|
+
|
|
28
|
+
/** Pick the Q size for a terminal. Under 60 cols the caller uses the half-block compact Q instead. */
|
|
29
|
+
export function qSize(cols, rows) {
|
|
30
|
+
if (cols < 60) return 'compact';
|
|
31
|
+
return cols >= 100 && rows >= 30 ? 'large' : 'mid';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Lab parity sizing: under 60 cols (or under 12 rows) the lab's compact particle Q, never a half-block. */
|
|
35
|
+
export function labSize(cols, rows) {
|
|
36
|
+
if (cols < 60 || rows < 12) return 'small';
|
|
37
|
+
return qSize(cols, rows);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function qDims(size) {
|
|
41
|
+
const H = SIZES[size];
|
|
42
|
+
const cells = Math.ceil((H * ASPECT) / 2);
|
|
43
|
+
return { H, W: cells * 2, cols: cells, rows: H / 4 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---- silhouette (unit space: y 0..1 top to bottom, x 0..ASPECT) -----------------------------------
|
|
47
|
+
const distSeg = (px, py, ax, ay, bx, by) => {
|
|
48
|
+
const dx = bx - ax, dy = by - ay;
|
|
49
|
+
const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)));
|
|
50
|
+
return [Math.sqrt((px - (ax + dx * t)) ** 2 + (py - (ay + dy * t)) ** 2), t];
|
|
51
|
+
};
|
|
52
|
+
const bez = (a, b, c, t) => [(1 - t) ** 2 * a[0] + 2 * (1 - t) * t * b[0] + t * t * c[0], (1 - t) ** 2 * a[1] + 2 * (1 - t) * t * b[1] + t * t * c[1]];
|
|
53
|
+
|
|
54
|
+
export const BOWL = { cx: ASPECT / 2, cy: 0.6, rx: 0.4, ry: 0.385, w: 0.118 };
|
|
55
|
+
// Each horn is a quadratic curve, thick at the base and tapering to a point: out and up like a bull's.
|
|
56
|
+
// The right horn is the exact mirror of the left about the bowl's axis (it was hand-tuned and its tip had
|
|
57
|
+
// drifted to y 0.0 against the left's 0.03, which read as a thin stalk next to a solid horn).
|
|
58
|
+
const LEFT_HORN = { a: [0.2, 0.29], b: [-0.02, 0.27], c: [0.02, 0.03], r0: 0.048 };
|
|
59
|
+
const mirrorX = (p) => [ASPECT - p[0], p[1]];
|
|
60
|
+
const HORNS = [LEFT_HORN, { a: mirrorX(LEFT_HORN.a), b: mirrorX(LEFT_HORN.b), c: mirrorX(LEFT_HORN.c), r0: LEFT_HORN.r0 }];
|
|
61
|
+
const HORN_PTS = HORNS.map((hn) => Array.from({ length: 24 }, (_, i) => ({ p: bez(hn.a, hn.b, hn.c, i / 23), t: i / 23, r0: hn.r0 })));
|
|
62
|
+
const TAIL = { a: [0.4, 0.69], b: [0.79, 0.985], r0: 0.07, r1: 0.04 };
|
|
63
|
+
|
|
64
|
+
/** Which part of the Q a unit point is in: null, or { part: 'bowl'|'tail'|'horn', t } (t = 0 base .. 1 tip). */
|
|
65
|
+
function partAt(x, y) {
|
|
66
|
+
const [d, t] = distSeg(x, y, ...TAIL.a, ...TAIL.b);
|
|
67
|
+
const inTail = d <= TAIL.r0 + (TAIL.r1 - TAIL.r0) * t;
|
|
68
|
+
const dx = (x - BOWL.cx) / BOWL.rx, dy = (y - BOWL.cy) / BOWL.ry;
|
|
69
|
+
const e = Math.sqrt(dx ** 2 + dy ** 2);
|
|
70
|
+
const inner = 1 - BOWL.w / ((BOWL.rx + BOWL.ry) / 2);
|
|
71
|
+
if (e <= 1 && e >= inner) return { part: inTail && t > 0.45 ? 'tail' : 'bowl', t: inTail ? t : 0 };
|
|
72
|
+
if (inTail) return { part: 'tail', t };
|
|
73
|
+
if (y > 0.34) return null;
|
|
74
|
+
for (const pts of HORN_PTS) for (const q of pts) if (Math.sqrt((x - q.p[0]) ** 2 + (y - q.p[1]) ** 2) <= q.r0 * (1 - q.t * 0.93) + 0.004) return { part: 'horn', t: q.t };
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const maskCache = new Map();
|
|
79
|
+
/**
|
|
80
|
+
* Boolean mask of the Q silhouette at a size: { W, H, ox, on(x, y), dots: [{ x, y, part, t }] }.
|
|
81
|
+
* ox is the left offset of the silhouette inside the cell grid (dots).
|
|
82
|
+
*/
|
|
83
|
+
export function qMask(size) {
|
|
84
|
+
if (maskCache.has(size)) return maskCache.get(size);
|
|
85
|
+
const { W, H } = qDims(size);
|
|
86
|
+
const ox = (W - H * ASPECT) / 2; // the Q's axis sits exactly on the grid centre, so the mask mirrors dot for dot
|
|
87
|
+
const dots = [];
|
|
88
|
+
const grid = new Uint8Array(W * H);
|
|
89
|
+
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
|
|
90
|
+
// 2x2 supersample for clean edges
|
|
91
|
+
let n = 0;
|
|
92
|
+
for (const [sx, sy] of [[0.25, 0.25], [0.75, 0.25], [0.25, 0.75], [0.75, 0.75]]) if (partAt((x - ox + sx) / H, (y + sy) / H)) n++;
|
|
93
|
+
if (n >= 2) {
|
|
94
|
+
grid[y * W + x] = 1;
|
|
95
|
+
const p = partAt((x - ox + 0.5) / H, (y + 0.5) / H) || { part: 'bowl', t: 0 };
|
|
96
|
+
dots.push({ x, y, part: p.part, t: p.t });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const m = { W, H, ox, on: (x, y) => grid[y * W + x] === 1, dots };
|
|
100
|
+
maskCache.set(size, m);
|
|
101
|
+
return m;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- intensity field -> cells -> text -----------------------------------------------------------------
|
|
105
|
+
const BR = [[0x1, 0x8], [0x2, 0x10], [0x4, 0x20], [0x40, 0x80]];
|
|
106
|
+
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
107
|
+
const smooth = (e) => { e = clamp(e); return e * e * (3 - 2 * e); };
|
|
108
|
+
const lerp = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t);
|
|
109
|
+
const RESET = '\x1b[0m';
|
|
110
|
+
|
|
111
|
+
/** A field of dot intensities. 0 = off, 1 = the settled Q, > 1 = a flash (pushes towards white). */
|
|
112
|
+
export function makeField(W, H) {
|
|
113
|
+
const v = new Float32Array(W * H);
|
|
114
|
+
return {
|
|
115
|
+
W, H, v,
|
|
116
|
+
set(x, y, val) {
|
|
117
|
+
x = Math.round(x); y = Math.round(y);
|
|
118
|
+
if (x < 0 || y < 0 || x >= W || y >= H) return;
|
|
119
|
+
const i = y * W + x; if (val > v[i]) v[i] = val;
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Turn a field into rows of cells { ch, rgb }. rgb is null for blanks and in mono.
|
|
126
|
+
* color false (mono): shapes only, dim dots (< 0.35) are dropped, as in the lab.
|
|
127
|
+
* quant true (terminal default): 4 hue bands x 6 brightness levels, so SGR runs stay long (Ink tokenising
|
|
128
|
+
* a per-cell colour was ~100 ms a frame); trails keep their own levels so the drag still reads.
|
|
129
|
+
* quant false (web): the lab's continuous shade, every cell its own colour.
|
|
130
|
+
* tint: one rgb for every cell instead of the sky to violet gradient (amber, red, ...).
|
|
131
|
+
*/
|
|
132
|
+
export function fieldToCells(field, { color = true, quant = true, tint = null } = {}) {
|
|
133
|
+
const { W, H, v } = field;
|
|
134
|
+
const thr = color ? 0.04 : 0.35;
|
|
135
|
+
const cols = W / 2, rows = H / 4, out = [];
|
|
136
|
+
for (let cy = 0; cy < rows; cy++) {
|
|
137
|
+
const row = [];
|
|
138
|
+
for (let cx = 0; cx < cols; cx++) {
|
|
139
|
+
let bits = 0, m = 0;
|
|
140
|
+
for (let dy = 0; dy < 4; dy++) for (let dx = 0; dx < 2; dx++) {
|
|
141
|
+
const val = v[(cy * 4 + dy) * W + cx * 2 + dx];
|
|
142
|
+
if (val > thr) { bits |= BR[dy][dx]; if (val > m) m = val; }
|
|
143
|
+
}
|
|
144
|
+
if (!bits) { row.push({ ch: ' ', rgb: null }); continue; }
|
|
145
|
+
let rgb = null;
|
|
146
|
+
if (color) {
|
|
147
|
+
if (quant) {
|
|
148
|
+
const base = tint || lerp(SKY, VIOLET, (Math.min(3, Math.floor((cx / cols) * 4)) + 0.5) / 4);
|
|
149
|
+
const [lum, fl] = m > 1.5 ? [1, 0.85] : m > 1.05 ? [1, 0.45] : m >= 0.95 ? [1, 0] : m > 0.6 ? [0.72, 0] : m > 0.38 ? [0.48, 0] : [0.3, 0];
|
|
150
|
+
rgb = base.map((c) => Math.round(c * lum + (255 - c * lum) * fl));
|
|
151
|
+
} else {
|
|
152
|
+
const base = tint || lerp(SKY, VIOLET, cols > 1 ? cx / (cols - 1) : 0);
|
|
153
|
+
const f = 0.28 + 0.72 * Math.min(1, m), fl = clamp(m - 1);
|
|
154
|
+
rgb = base.map((c) => { c *= f; return Math.round(c + (255 - c) * fl * 0.9); });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
row.push({ ch: String.fromCharCode(0x2800 + bits), rgb });
|
|
158
|
+
}
|
|
159
|
+
out.push(row);
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const sgr = (rgb, bg = false) => `\x1b[${bg ? 48 : 38};2;${rgb.map((x) => Math.max(0, Math.min(255, Math.round(x)))).join(';')}m`;
|
|
165
|
+
/**
|
|
166
|
+
* Cells -> ANSI lines (truecolor SGR, one escape per colour run). Mono cells carry no SGR at all. A cell may
|
|
167
|
+
* also carry `bg` (the dragon's eyes are quadrant blocks with a foreground AND a background colour); a
|
|
168
|
+
* background run is closed with a reset before the next cell without one, so it never bleeds. Cells without
|
|
169
|
+
* bg render exactly as they always have.
|
|
170
|
+
*/
|
|
171
|
+
export function cellsToAnsi(rows) {
|
|
172
|
+
return rows.map((row) => {
|
|
173
|
+
let s = '', last = '', lastBg = '';
|
|
174
|
+
for (const c of row) {
|
|
175
|
+
const b = c.bg ? sgr(c.bg, true) : '';
|
|
176
|
+
if (lastBg && !b) { s += RESET; last = ''; lastBg = ''; }
|
|
177
|
+
if (c.rgb) { const e = sgr(c.rgb); if (e !== last) { s += e; last = e; } }
|
|
178
|
+
else if (last && c.ch !== ' ') { s += RESET; last = ''; if (lastBg) lastBg = ''; }
|
|
179
|
+
if (b && b !== lastBg) { s += b; lastBg = b; }
|
|
180
|
+
s += c.ch;
|
|
181
|
+
}
|
|
182
|
+
return s + (last || lastBg ? RESET : '');
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/** Cells -> plain text lines. */
|
|
186
|
+
export const cellsToText = (rows) => rows.map((r) => r.map((c) => c.ch).join(''));
|
|
187
|
+
|
|
188
|
+
const rng = (seed) => () => { seed |= 0; seed = (seed + 0x6d2b79f5) | 0; let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
|
|
189
|
+
|
|
190
|
+
// ---- the settled Q ------------------------------------------------------------------------------------
|
|
191
|
+
/** Cells of the settled Q. */
|
|
192
|
+
export function qCells(size, opts = {}) {
|
|
193
|
+
const m = qMask(size), f = makeField(m.W, m.H);
|
|
194
|
+
for (const d of m.dots) f.v[d.y * m.W + d.x] = 1;
|
|
195
|
+
return fieldToCells(f, opts);
|
|
196
|
+
}
|
|
197
|
+
/** Rows of text for the settled Q (identical to orbitFrame(size, 1)). */
|
|
198
|
+
export function qLines(size, { color = true } = {}) { return cellsToAnsi(qCells(size, { color })); }
|
|
199
|
+
|
|
200
|
+
// ---- 1: Orbit & Lock (and 3: Particle collapse) ---------------------------------------------------------
|
|
201
|
+
/** The intro's whole budget, ms. Orbit runs ORBIT_MS of it; the rest holds the finished mark. */
|
|
202
|
+
export const INTRO_MS = 900;
|
|
203
|
+
export const ORBIT_MS = 820;
|
|
204
|
+
const FLASH_MS = 170;
|
|
205
|
+
// Lab parameters, verbatim (lab.mjs build()): orbit for large/mid, particle for the compact Q.
|
|
206
|
+
const SWARM = {
|
|
207
|
+
orbit: { band: 0.3, appearMax: 0.08, lockStart: 0.42, lockSpan: 0.2, lockLen: 0.2, byAngle: true, spin: [0.5, 5] },
|
|
208
|
+
particle: { band: 0.05, appearMax: 0.5, lockStart: 0.55, lockSpan: 0.12, lockLen: 0.2, byAngle: false, spin: [1.2, 2.5] },
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const swarmCache = new Map();
|
|
212
|
+
function swarm(size, kind, seed) {
|
|
213
|
+
const key = `${size}:${kind}:${seed}`;
|
|
214
|
+
if (swarmCache.has(key)) return swarmCache.get(key);
|
|
215
|
+
const P = SWARM[kind], m = qMask(size), R = rng(seed + m.H);
|
|
216
|
+
const cx = m.W / 2, cy = m.H / 2, Rmax = Math.min(m.W, m.H) / 2 - 1.5;
|
|
217
|
+
const parts = m.dots.map((d) => ({
|
|
218
|
+
tx: d.x, ty: d.y, part: d.part, pt: d.t, ang: Math.atan2(d.y - cy, d.x - cx), th0: R() * TAU,
|
|
219
|
+
rho: Rmax - Rmax * P.band * R() ** 2, w: 0.85 + 0.3 * R(), ap: R() * P.appearMax, rnd: R(),
|
|
220
|
+
}));
|
|
221
|
+
const key2 = (p) => (P.byAngle ? p.ang : p.rnd);
|
|
222
|
+
[...parts].sort((a, b) => key2(a) - key2(b)).forEach((p, i) => { p.s = P.lockStart + P.lockSpan * (i / (parts.length - 1)); });
|
|
223
|
+
// Tail sweep: the tail is the last stroke to land, base to tip, so the Q finishes with a flourish.
|
|
224
|
+
// Horns land tip-last, so each horn grows out of the bowl with its flash travelling up to the point.
|
|
225
|
+
const end = P.lockStart + P.lockSpan;
|
|
226
|
+
for (const p of parts) {
|
|
227
|
+
if (p.part === 'tail') p.s = end - 0.06 + 0.08 * p.pt;
|
|
228
|
+
else if (p.part === 'horn') p.s = Math.min(p.s, end - 0.1) + 0.05 * p.pt;
|
|
229
|
+
}
|
|
230
|
+
const r = { P, parts, cx, cy };
|
|
231
|
+
swarmCache.set(key, r);
|
|
232
|
+
return r;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The swarm field at t ms. Each particle draws a head plus two trail samples that lag along its path; the
|
|
237
|
+
* lag grows with speed (0.09 * (1 + 2u) rad), which is the "drag" in the lab: fast particles smear.
|
|
238
|
+
*/
|
|
239
|
+
function swarmField(size, t, { kind = 'orbit', dur = ORBIT_MS, seed = 11 } = {}) {
|
|
240
|
+
const m = qMask(size), { P, parts, cx, cy } = swarm(size, kind, seed), f = makeField(m.W, m.H);
|
|
241
|
+
const u = t / dur, settled = t > dur + FLASH_MS + 300;
|
|
242
|
+
const ph = TAU * (P.spin[0] * u + P.spin[1] * u * u);
|
|
243
|
+
for (const p of parts) {
|
|
244
|
+
const e = settled ? 1 : smooth((u - p.s) / P.lockLen);
|
|
245
|
+
if (e >= 1) {
|
|
246
|
+
const lt = (u - (p.s + P.lockLen)) * dur;
|
|
247
|
+
f.set(p.tx, p.ty, settled ? 1 : 1 + Math.max(0, 1 - lt / FLASH_MS) * 0.9);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (u < p.ap) continue;
|
|
251
|
+
const fade = Math.min(1, ((u - p.ap) * dur) / 90);
|
|
252
|
+
for (let k = 0; k < 3; k++) {
|
|
253
|
+
const th = p.th0 + (ph - k * 0.09 * (1 + 2 * u)) * p.w;
|
|
254
|
+
const x = cx + p.rho * Math.cos(th), y = cy + p.rho * Math.sin(th);
|
|
255
|
+
f.set(x + (p.tx - x) * e, y + (p.ty - y) * e, (k === 0 ? 0.9 : k === 1 ? 0.5 : 0.28) * fade + (e > 0 && k === 0 ? 0.4 * e : 0));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return f;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Orbit & Lock cells at t ms (0..INTRO_MS; >= INTRO_MS is the settled Q). small plays the particle collapse. */
|
|
262
|
+
export function orbitCellsAt(size, t, opts = {}) {
|
|
263
|
+
if (t >= INTRO_MS) return qCells(size, opts);
|
|
264
|
+
const kind = size === 'small' ? 'particle' : 'orbit';
|
|
265
|
+
return fieldToCells(swarmField(size, t, { kind, dur: kind === 'particle' ? INTRO_MS - 60 : ORBIT_MS, seed: opts.seed ?? 11 }), opts);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* One frame of Orbit & Lock at progress 0..1 of INTRO_MS (lab timing: 820 ms orbit, all locks done by
|
|
270
|
+
* 672 ms, the last flashes fade by ~840 ms). progress >= 1 is exactly the settled Q.
|
|
271
|
+
*/
|
|
272
|
+
export function orbitFrame(size, progress, { color = true } = {}) {
|
|
273
|
+
if (progress >= 1) return qLines(size, { color });
|
|
274
|
+
return cellsToAnsi(orbitCellsAt(size, progress * INTRO_MS, { color }));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Particle collapse at t ms of `dur` (the lab's compact variant, any size). */
|
|
278
|
+
export function particleCellsAt(size, t, opts = {}) {
|
|
279
|
+
const dur = opts.dur ?? 1100;
|
|
280
|
+
if (t >= dur + FLASH_MS + 300) return qCells(size, opts);
|
|
281
|
+
return fieldToCells(swarmField(size, t, { kind: 'particle', dur, seed: opts.seed ?? 12 }), opts);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---- 2: Portal -------------------------------------------------------------------------------------------
|
|
285
|
+
export const PORTAL_MS = 1000;
|
|
286
|
+
const portalCache = new Map();
|
|
287
|
+
function portalModel(size) {
|
|
288
|
+
if (portalCache.has(size)) return portalCache.get(size);
|
|
289
|
+
const m = qMask(size), H = m.H;
|
|
290
|
+
const ccx = m.ox + BOWL.cx * H, ccy = BOWL.cy * H, ringR = 0.3 * H, revR = 0.42 * H;
|
|
291
|
+
const phiAt = (u) => (u < 0.3 ? (Math.PI / 2) * smooth(u / 0.3) : u < 0.7 ? Math.PI / 2 + (Math.PI / 2) * smooth((u - 0.3) / 0.4) : Math.PI);
|
|
292
|
+
const pts = m.dots.map((d) => {
|
|
293
|
+
// The tail sweeps in last, base to tip (lab: rt 0.74 .. 0.94 along the tail).
|
|
294
|
+
if (d.part === 'tail') return { ...d, rt: 0.74 + 0.2 * d.t };
|
|
295
|
+
let rt = 0.74 + 0.1 * (d.y / H);
|
|
296
|
+
for (let u = 0.3; u <= 0.7; u += 0.004) {
|
|
297
|
+
const w = Math.max(0.02, Math.abs(Math.cos(phiAt(u))));
|
|
298
|
+
if (((d.x - ccx) / (revR * w)) ** 2 + ((d.y - ccy) / revR) ** 2 <= 1) { rt = u; break; }
|
|
299
|
+
}
|
|
300
|
+
return { ...d, rt };
|
|
301
|
+
});
|
|
302
|
+
const r = { m, ccx, ccy, ringR, phiAt, pts };
|
|
303
|
+
portalCache.set(size, r);
|
|
304
|
+
return r;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Portal at t ms: a ring spins in 3D (circle, edge-on line, open), the Q fills from the opening, tail last. */
|
|
308
|
+
export function portalCellsAt(size, t, opts = {}) {
|
|
309
|
+
const { m, ccx, ccy, ringR, phiAt, pts } = portalModel(size), f = makeField(m.W, m.H);
|
|
310
|
+
const dur = PORTAL_MS, u = t / dur, settled = t > dur + 500, phi = phiAt(Math.min(u, 1));
|
|
311
|
+
if (settled) return qCells(size, opts);
|
|
312
|
+
const alpha = Math.min(1, u / 0.1) * (1 - clamp((u - 0.68) / 0.12));
|
|
313
|
+
if (alpha > 0) {
|
|
314
|
+
const w = Math.cos(phi), n = Math.round(ringR * 24);
|
|
315
|
+
for (let i = 0; i < n; i++) {
|
|
316
|
+
const th = (i / n) * TAU, z = Math.cos(th) * Math.sin(phi);
|
|
317
|
+
f.set(ccx + ringR * Math.cos(th) * w, ccy + ringR * Math.sin(th), alpha * (0.62 + 0.38 * z));
|
|
318
|
+
}
|
|
319
|
+
if (Math.abs(w) < 0.12) for (let yy = -ringR; yy <= ringR; yy++) f.set(ccx, ccy + yy, alpha * 1.15);
|
|
320
|
+
}
|
|
321
|
+
for (const p of pts) if (u >= p.rt) f.set(p.x, p.y, 1 + Math.max(0, 1 - ((u - p.rt) * dur) / 150) * 0.7);
|
|
322
|
+
return fieldToCells(f, opts);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---- resting --------------------------------------------------------------------------------------------
|
|
326
|
+
export const BREATH_MS = 2400;
|
|
327
|
+
/** The settled Q breathing gently (idle on the web face): brightness 0.82 .. 1 over BREATH_MS. */
|
|
328
|
+
export function restCellsAt(size, t, opts = {}) {
|
|
329
|
+
const m = qMask(size), f = makeField(m.W, m.H);
|
|
330
|
+
const g = 0.82 + 0.18 * (0.5 + 0.5 * Math.sin((t / BREATH_MS) * TAU - Math.PI / 2));
|
|
331
|
+
for (const d of m.dots) f.v[d.y * m.W + d.x] = g;
|
|
332
|
+
return fieldToCells(f, opts);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ---- intro plan ------------------------------------------------------------------------------------------
|
|
336
|
+
/**
|
|
337
|
+
* The intro as one timeline: Orbit & Lock, then the settled Q. Returns { ms, at(t) -> cells }. Callers drive t
|
|
338
|
+
* from their own clock.
|
|
339
|
+
*/
|
|
340
|
+
export function introPlan(size, opts = {}) {
|
|
341
|
+
return {
|
|
342
|
+
ms: INTRO_MS,
|
|
343
|
+
at(t) {
|
|
344
|
+
if (t < INTRO_MS) return orbitCellsAt(size, t, opts);
|
|
345
|
+
return qCells(size, opts);
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// Quox eyes (canonical source: quox cli/lib/quoxon/qeyes.js; the cli.quox.ai site vendors this file as is).
|
|
2
|
+
// Two friendly cartoon eyes in the Q's own terminal cell pipeline, so the web (cellsToHTML) and the
|
|
3
|
+
// terminal (cellsToAnsi) draw identical eyes. Two techniques, both terminal-safe:
|
|
4
|
+
// dots Braille dots (the Q's own particles): a closed disc of white dots, a pupil of UNLIT dots (the
|
|
5
|
+
// background shows through, so it reads black), a 2x2 dot glint, and a 2-dot arc when closed.
|
|
6
|
+
// blocks Unicode quadrant blocks with a foreground AND a background colour (as qdragon's eye layer).
|
|
7
|
+
// Plus motion as pure functions of time: blink, look around, idle (eyeState).
|
|
8
|
+
//
|
|
9
|
+
// PURE AND PORTABLE: no DOM, no timers, no Node APIs. A function of (geometry, state) or (mode, t).
|
|
10
|
+
// The left eye is computed once and the right eye is the same eye TRANSLATED (both centres snapped to even
|
|
11
|
+
// dots, so both sit on the same Braille cell phase): the two always match, and both pupils look the same way.
|
|
12
|
+
// Compositing is per cell: a cell inside an eye's footprint is drawn from the eye layer only, so a Q dot never
|
|
13
|
+
// shares a cell (and its one colour) with an eye. The Q itself is never redrawn.
|
|
14
|
+
//
|
|
15
|
+
// Geometry is the Q's own unit space (qart.js): y 0..1 down the Q, x 0..0.82, the bowl's axis at x 0.41.
|
|
16
|
+
|
|
17
|
+
const AXIS = 0.41;
|
|
18
|
+
const BR = [[0x1, 0x8], [0x2, 0x10], [0x4, 0x20], [0x40, 0x80]];
|
|
19
|
+
const QUAD = [' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛', '▗', '▚', '▐', '▜', '▄', '▙', '▟', '█'];
|
|
20
|
+
export const EYE_WHITE = [255, 255, 255]; // pure white (owner: whiter whites)
|
|
21
|
+
export const EYE_PUPIL = [10, 12, 18];
|
|
22
|
+
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
23
|
+
const smooth = (e) => { e = clamp(e); return e * e * (3 - 2 * e); };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The owner's pick ("big and googly"). r, sep, y in Q units. pupil: pupil radius as a share of r. rest: where the
|
|
27
|
+
* pupils rest (gaze -1..1; a little down reads as "looking at you"). rim: the least white (dots) kept round the
|
|
28
|
+
* pupil at every gaze. glint: the 2x2 glint's offset from the pupil centre, as a share of the pupil radius (out
|
|
29
|
+
* towards the upper left, near the pupil's edge, so it reads as a highlight and not a bullseye). sag: closed lid.
|
|
30
|
+
*/
|
|
31
|
+
export const GOOGLY = { r: 0.104, sep: 0.124, y: 0.53, ox: 0.95, oy: 1.02, pupil: 0.46, rest: [0, 1], rim: 2, glint: 0.62, sag: 0.34 };
|
|
32
|
+
/** Variant A of the bullseye fix (rejected by screenshot: the thinner rim still read as a donut). */
|
|
33
|
+
export const GOOGLY_A = { ...GOOGLY, pupil: 0.52 };
|
|
34
|
+
/** The v2 dots (before the bullseye fix): smaller pupil, glint near its centre. Kept for comparison. */
|
|
35
|
+
export const GOOGLY_V2 = { ...GOOGLY, pupil: 0.43, glint: 0.45, rest: [0, 0.6] };
|
|
36
|
+
|
|
37
|
+
function centres(geo, v) {
|
|
38
|
+
const R = v.r * geo.Hq, axis = 2 * Math.round((geo.ox + AXIS * geo.Hq) / 2), cy = (geo.oy ?? 0) + v.y * geo.Hq;
|
|
39
|
+
const cxL = axis - 2 * Math.ceil((v.sep * geo.Hq) / 2);
|
|
40
|
+
return { R, cy, cxL, shift: 2 * (axis - cxL) }; // right eye = left eye + shift (an even number of dots)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The left eye in dot units: f(x, y) -> 0 none | 1 white | 2 pupil | 3 glint, for an open eye. */
|
|
44
|
+
function eyeShape(cx, cy, R, v, open, gx, gy) {
|
|
45
|
+
const rx = R * v.ox, ry = R * v.oy, pr = R * v.pupil;
|
|
46
|
+
const lidY = cy - ry + (1 - open) * 2 * ry * 0.92; // the upper lid: everything above it is closed
|
|
47
|
+
// the pupil travels inside the white, shrunk by its radius plus the rim, and snaps to whole dots
|
|
48
|
+
const ax = Math.max(0, rx - pr - v.rim), ay = Math.max(0, ry - pr - v.rim), n = Math.hypot(gx, gy), s = n > 1 ? 1 / n : 1;
|
|
49
|
+
const px = cx + Math.trunc(gx * s * ax), py = cy + Math.trunc(gy * s * ay); // truncate: never into the rim
|
|
50
|
+
const glint = pr >= 2.6, g = pr * v.glint * 0.75, gx0 = Math.round(px - g), gy0 = Math.round(py - g); // up-left, a dark dot clear of the rim
|
|
51
|
+
return (x, y) => {
|
|
52
|
+
if (((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2 > 1 || y < lidY) return 0;
|
|
53
|
+
if (y < lidY + 2) return 1; // the lid's edge: a 2-dot white line
|
|
54
|
+
if (Math.hypot(x - px, y - py) > pr) return 1;
|
|
55
|
+
if (glint && Math.floor(x) >= gx0 && Math.floor(x) <= gx0 + 1 && Math.floor(y) >= gy0 && Math.floor(y) <= gy0 + 1) return 3;
|
|
56
|
+
return 2;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** The closed lid's centre line y(x) (an arc bowed upwards, like a shut lid; owner flipped it from a smile) and its half-width, in dot units. */
|
|
60
|
+
function lid(cx, cy, R, v) {
|
|
61
|
+
const w = R * v.ox * 0.88, d = R >= 6 ? R * v.sag : 0.6, y0 = cy - d / 2;
|
|
62
|
+
return { n: Math.round(w), y: (x) => { const u = (x - cx) / w; return y0 + d * u * u; } };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Footprint: the cells each eye owns (its white ellipse plus a little margin), as a Uint8Array cols*rows. */
|
|
66
|
+
function footprint(geo, v) {
|
|
67
|
+
const { R, cy, cxL, shift } = centres(geo, v), foot = new Uint8Array(geo.cols * geo.rows);
|
|
68
|
+
for (const cx of [cxL, cxL + shift]) for (let r = 0; r < geo.rows; r++) for (let c = 0; c < geo.cols; c++) {
|
|
69
|
+
const x = c * 2 + 1, y = r * 4 + 2;
|
|
70
|
+
if (((x - cx) / (R * v.ox + 0.4)) ** 2 + ((y - cy) / (R * v.oy + 0.4)) ** 2 <= 1) foot[r * geo.cols + c] = 1;
|
|
71
|
+
}
|
|
72
|
+
return foot;
|
|
73
|
+
}
|
|
74
|
+
const gaze = (state, v) => [state.gx ?? v.rest[0], state.gy ?? v.rest[1]];
|
|
75
|
+
|
|
76
|
+
// ---- dots ---------------------------------------------------------------------------------------------------
|
|
77
|
+
/** Lit dots of both eyes on the W x H dot grid (Uint8Array, 1 = lit). state: { open 0..1, gx, gy -1..1 }. */
|
|
78
|
+
export function eyeDots(geo, state = {}, v = GOOGLY) {
|
|
79
|
+
const W = geo.cols * 2, H = geo.rows * 4, lit = new Uint8Array(W * H);
|
|
80
|
+
const { R, cy, cxL, shift } = centres(geo, v), open = clamp(state.open ?? 1);
|
|
81
|
+
const set = (x, y) => { for (const xx of [x, x + shift]) if (xx >= 0 && y >= 0 && xx < W && y < H) lit[y * W + xx] = 1; };
|
|
82
|
+
if (open < 0.15) {
|
|
83
|
+
const L = lid(cxL, cy, R, v); // one clean arc, 2 dots thick, symmetric about the eye centre
|
|
84
|
+
for (let x = cxL - L.n; x < cxL + L.n; x++) { const y = Math.round(L.y(x + 0.5) - 0.5); set(x, y); set(x, y + 1); }
|
|
85
|
+
return lit;
|
|
86
|
+
}
|
|
87
|
+
const [gx, gy] = gaze(state, v), f = eyeShape(cxL, cy, R, v, open, gx, gy);
|
|
88
|
+
for (let y = Math.floor(cy - R * 2); y <= Math.ceil(cy + R * 2); y++) for (let x = Math.floor(cxL - R * 2); x <= Math.ceil(cxL + R * 2); x++) {
|
|
89
|
+
const k = f(x + 0.5, y + 0.5);
|
|
90
|
+
if (k === 1 || k === 3) set(x, y);
|
|
91
|
+
}
|
|
92
|
+
return lit;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The dot eye layer: rows of cells (null = no eye here). */
|
|
96
|
+
export function eyeCells(geo, state, v = GOOGLY) {
|
|
97
|
+
const W = geo.cols * 2, lit = eyeDots(geo, state, v), foot = footprint(geo, v), rows = [];
|
|
98
|
+
for (let r = 0; r < geo.rows; r++) {
|
|
99
|
+
const row = [];
|
|
100
|
+
for (let c = 0; c < geo.cols; c++) {
|
|
101
|
+
let bits = 0;
|
|
102
|
+
for (let dy = 0; dy < 4; dy++) for (let dx = 0; dx < 2; dx++) if (lit[(r * 4 + dy) * W + c * 2 + dx]) bits |= BR[dy][dx];
|
|
103
|
+
row.push(bits ? { ch: String.fromCharCode(0x2800 + bits), rgb: EYE_WHITE } : foot[r * geo.cols + c] ? { ch: ' ', rgb: null } : null);
|
|
104
|
+
}
|
|
105
|
+
rows.push(row);
|
|
106
|
+
}
|
|
107
|
+
return rows;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---- blocks (reference) -------------------------------------------------------------------------------------
|
|
111
|
+
/** The block eye layer: 2x2 quadrant pixels per cell (a pixel is 1 dot wide, 2 tall), fg + bg colours. */
|
|
112
|
+
export function blockEyeCells(geo, state = {}, v = GOOGLY) {
|
|
113
|
+
const PW = geo.cols * 2, PH = geo.rows * 2, px = new Uint8Array(PW * PH); // 0 none, 1 white, 2 pupil
|
|
114
|
+
const { R, cy, cxL, shift } = centres(geo, v), open = clamp(state.open ?? 1);
|
|
115
|
+
const put = (x, y, k) => { for (const xx of [x, x + shift]) if (xx >= 0 && y >= 0 && xx < PW && y < PH) px[y * PW + xx] = k; };
|
|
116
|
+
if (open < 0.15) {
|
|
117
|
+
const L = lid(cxL, cy, R, v);
|
|
118
|
+
for (let x = cxL - L.n; x < cxL + L.n; x++) put(x, Math.floor(L.y(x + 0.5) / 2), 1);
|
|
119
|
+
} else {
|
|
120
|
+
const [gx, gy] = gaze(state, v), f = eyeShape(cxL, cy, R, { ...v, pupil: 0.43 }, open, gx, gy), small = R < 6;
|
|
121
|
+
for (let y = Math.floor((cy - R * 2) / 2); y <= Math.ceil((cy + R * 2) / 2); y++) for (let x = Math.floor(cxL - R * 2); x <= Math.ceil(cxL + R * 2); x++) {
|
|
122
|
+
const k = f(x + 0.5, y * 2 + 1);
|
|
123
|
+
if (k) put(x, y, k === 3 ? 1 : k === 2 && small ? 1 : k);
|
|
124
|
+
}
|
|
125
|
+
if (small) { const pr0 = Math.round(cy / 2); for (const x of [cxL - 1, cxL]) for (const y of [pr0 - 1, pr0]) put(x, y, 2); }
|
|
126
|
+
}
|
|
127
|
+
const foot = footprint(geo, v), rows = [];
|
|
128
|
+
for (let r = 0; r < geo.rows; r++) {
|
|
129
|
+
const row = [];
|
|
130
|
+
for (let c = 0; c < geo.cols; c++) {
|
|
131
|
+
const q = [px[(r * 2) * PW + c * 2], px[(r * 2) * PW + c * 2 + 1], px[(r * 2 + 1) * PW + c * 2], px[(r * 2 + 1) * PW + c * 2 + 1]];
|
|
132
|
+
if (!q.some((k) => k)) { row.push(foot[r * geo.cols + c] ? { ch: ' ', rgb: null } : null); continue; }
|
|
133
|
+
let m = 0; q.forEach((k, i) => { if (k === 1) m |= 1 << i; });
|
|
134
|
+
if (q.includes(1)) row.push({ ch: QUAD[m], rgb: EYE_WHITE, bg: q.includes(2) ? EYE_PUPIL : null });
|
|
135
|
+
else row.push({ ch: '█', rgb: EYE_PUPIL });
|
|
136
|
+
}
|
|
137
|
+
rows.push(row);
|
|
138
|
+
}
|
|
139
|
+
return rows;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- motion: pure functions of time ---------------------------------------------------------------------------
|
|
143
|
+
export const BLINK_MS = 200;
|
|
144
|
+
/** Lid openness u ms into a blink: shut in 70 ms, held 40, open again by 200. */
|
|
145
|
+
export function blinkOpen(u) {
|
|
146
|
+
if (u < 0 || u >= BLINK_MS) return 1;
|
|
147
|
+
if (u < 70) return 1 - smooth(u / 70);
|
|
148
|
+
if (u < 110) return 0;
|
|
149
|
+
return smooth((u - 110) / 90);
|
|
150
|
+
}
|
|
151
|
+
const hash = (n) => { let x = Math.imul((n | 0) ^ 0x9e3779b9, 0x85ebca6b); x ^= x >>> 13; x = Math.imul(x, 0xc2b2ae35); x ^= x >>> 16; return (x >>> 0) / 4294967296; };
|
|
152
|
+
// keyframes [[ms, [gx, gy]], ...], eased between neighbours, held at the ends
|
|
153
|
+
function gazeKf(t, keys) {
|
|
154
|
+
if (t <= keys[0][0]) return keys[0][1];
|
|
155
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
156
|
+
const [t0, a] = keys[i], [t1, b] = keys[i + 1];
|
|
157
|
+
if (t < t1) { const e = smooth((t - t0) / (t1 - t0)); return [a[0] + (b[0] - a[0]) * e, a[1] + (b[1] - a[1]) * e]; }
|
|
158
|
+
}
|
|
159
|
+
return keys[keys.length - 1][1];
|
|
160
|
+
}
|
|
161
|
+
const REST = GOOGLY.rest;
|
|
162
|
+
/** Look around: centre, left, right, up, down, centre; 260 ms moves, 600 ms holds. */
|
|
163
|
+
export const LOOK_KEYS = (() => {
|
|
164
|
+
const pts = [REST, [-1, 0.2], [1, 0.2], [0, -1], [0, 1], REST], keys = [];
|
|
165
|
+
let t = 0;
|
|
166
|
+
for (const p of pts) { keys.push([t, p]); t += 600; keys.push([t, p]); t += 260; }
|
|
167
|
+
return keys;
|
|
168
|
+
})();
|
|
169
|
+
export const LOOK_MS = LOOK_KEYS[LOOK_KEYS.length - 1][0] + 400;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The eyes' state at t ms for a mode: 'open' | 'closed' | 'blink' | 'look' | 'idle'. Returns { open, gx, gy }.
|
|
173
|
+
* blink a blink every 1.6 s; every third one is a double blink.
|
|
174
|
+
* look the look-around loop (LOOK_KEYS).
|
|
175
|
+
* idle blinks every 2.6 to 5 s (one in five a double), and now and then a glance that holds and returns.
|
|
176
|
+
*/
|
|
177
|
+
export function eyeState(mode, t) {
|
|
178
|
+
if (mode === 'closed') return { open: 0, gx: REST[0], gy: REST[1] };
|
|
179
|
+
if (mode === 'open' || t < 0) return { open: 1, gx: REST[0], gy: REST[1] };
|
|
180
|
+
if (mode === 'blink') {
|
|
181
|
+
const k = Math.floor(t / 1600), u = t - k * 1600;
|
|
182
|
+
return { open: Math.min(blinkOpen(u - 300), k % 3 === 2 ? blinkOpen(u - 560) : 1), gx: REST[0], gy: REST[1] };
|
|
183
|
+
}
|
|
184
|
+
if (mode === 'look') { const [gx, gy] = gazeKf(t % LOOK_MS, LOOK_KEYS); return { open: 1, gx, gy }; }
|
|
185
|
+
// idle: a seeded schedule of events, each 2.6 to 5 s long, so any t maps to the same frame
|
|
186
|
+
let start = 0, i = 0;
|
|
187
|
+
for (;;) { const len = 2600 + hash(i * 7 + 1) * 2400; if (t < start + len) break; start += len; i++; if (i > 100000) break; }
|
|
188
|
+
const u = t - start, len = 2600 + hash(i * 7 + 1) * 2400;
|
|
189
|
+
let open = 1, g = REST;
|
|
190
|
+
if (hash(i * 7 + 2) < 0.4) {
|
|
191
|
+
// a glance: move in 240 ms, hold, come back before the event ends
|
|
192
|
+
const dir = [[-1, 0.2], [1, 0.2], [-0.8, -0.7], [0.8, -0.7], [0, 1]][Math.floor(hash(i * 7 + 3) * 5)];
|
|
193
|
+
g = gazeKf(u, [[300, REST], [540, dir], [540 + 900, dir], [540 + 900 + 260, REST]]);
|
|
194
|
+
}
|
|
195
|
+
const b = len - 700; // the blink near the end of each event
|
|
196
|
+
open = Math.min(blinkOpen(u - b), hash(i * 7 + 4) < 0.2 ? blinkOpen(u - b - 260) : 1);
|
|
197
|
+
return { open, gx: g[0], gy: g[1] };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---- compose ------------------------------------------------------------------------------------------------
|
|
201
|
+
/** Composite: eye cells over base cells (same grid). Base cells are untouched wherever the eye layer is null. */
|
|
202
|
+
export function overlay(base, eyes) {
|
|
203
|
+
return base.map((row, r) => row.map((cell, c) => (eyes[r] && eyes[r][c]) || cell));
|
|
204
|
+
}
|
|
205
|
+
const layer = (style) => (style === 'blocks' ? blockEyeCells : eyeCells);
|
|
206
|
+
/** Eyes for the settled qart Q, given its mask ({ W, H, ox } from qMask). style: 'dots' | 'blocks'. */
|
|
207
|
+
export function eyesForQ(mask, state, v = GOOGLY, style = 'dots') {
|
|
208
|
+
return layer(style)({ cols: mask.W / 2, rows: mask.H / 4, ox: mask.ox, Hq: mask.H }, state, v);
|
|
209
|
+
}
|
|
210
|
+
/** Eyes for a qdragon layout ({ cols, rows, qx0, qy0, H }). */
|
|
211
|
+
export function eyesForDragon(L, state, v = GOOGLY, style = 'dots') {
|
|
212
|
+
return layer(style)({ cols: L.cols, rows: L.rows, ox: L.qx0, oy: L.qy0, Hq: L.H }, state, v);
|
|
213
|
+
}
|