@zergai/ztc 0.4.0-beta.1 → 0.5.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +398 -45
- package/RUST_THIRD_PARTY_LICENSES.txt +1080 -822
- package/THIRD_PARTY_NOTICES.md +273 -12
- package/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-audio-darwin-arm64 +0 -0
- package/bin/ztc-capture-darwin-arm64 +0 -0
- package/bin/ztc-computer-darwin-arm64 +0 -0
- package/bin/ztc-llm-darwin-arm64 +0 -0
- package/bin/ztc-tts-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/ztc-zlicky-overlay-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/App.js +1206 -209
- package/dist/agent/agent.d.ts +74 -8
- package/dist/agent/agent.js +1073 -184
- package/dist/agent/backends/account_rotation.d.ts +4 -0
- package/dist/agent/backends/account_rotation.js +8 -2
- package/dist/agent/backends/anthropic.d.ts +13 -0
- package/dist/agent/backends/anthropic.js +169 -28
- package/dist/agent/backends/claude_cli.js +18 -8
- package/dist/agent/backends/codex_app_server.d.ts +1 -0
- package/dist/agent/backends/codex_app_server.js +55 -3
- package/dist/agent/backends/codex_auth.d.ts +1 -0
- package/dist/agent/backends/codex_auth.js +67 -13
- package/dist/agent/backends/codex_buffered_recovery.d.ts +27 -0
- package/dist/agent/backends/codex_buffered_recovery.js +293 -0
- package/dist/agent/backends/gemini.js +3 -1
- package/dist/agent/backends/http_error.d.ts +26 -0
- package/dist/agent/backends/http_error.js +221 -0
- package/dist/agent/backends/index.d.ts +9 -2
- package/dist/agent/backends/index.js +22 -2
- package/dist/agent/backends/openai.d.ts +0 -6
- package/dist/agent/backends/openai.js +5 -2
- package/dist/agent/backends/openai_compatible.d.ts +5 -4
- package/dist/agent/backends/openai_compatible.js +26 -19
- package/dist/agent/backends/openai_responses.d.ts +8 -3
- package/dist/agent/backends/openai_responses.js +77 -33
- package/dist/agent/backends/provider_ids.d.ts +1 -0
- package/dist/agent/backends/provider_ids.js +4 -0
- package/dist/agent/backends/request_metrics.js +12 -2
- package/dist/agent/backends/types.d.ts +109 -6
- package/dist/agent/backends/types.js +5 -1
- package/dist/agent/backends/zrouter_stream.d.ts +6 -1
- package/dist/agent/backends/zrouter_stream.js +160 -42
- package/dist/agent/background_registry.d.ts +2 -0
- package/dist/agent/background_registry.js +1 -0
- package/dist/agent/claim_observation.d.ts +6 -0
- package/dist/agent/claim_observation.js +54 -0
- package/dist/agent/commands/accounts.js +46 -177
- package/dist/agent/commands/attention.d.ts +2 -0
- package/dist/agent/commands/attention.js +67 -0
- package/dist/agent/commands/auth.js +18 -2
- package/dist/agent/commands/board.d.ts +2 -0
- package/dist/agent/commands/board.js +169 -0
- package/dist/agent/commands/clipboard.js +50 -1
- package/dist/agent/commands/compact.js +21 -13
- package/dist/agent/commands/config.js +4 -1
- package/dist/agent/commands/context.js +6 -1
- package/dist/agent/commands/desktop.d.ts +2 -0
- package/dist/agent/commands/desktop.js +29 -0
- package/dist/agent/commands/effort.js +14 -6
- package/dist/agent/commands/environment.d.ts +3 -0
- package/dist/agent/commands/environment.js +109 -0
- package/dist/agent/commands/evidence.d.ts +2 -0
- package/dist/agent/commands/evidence.js +9 -0
- package/dist/agent/commands/fork.js +4 -3
- package/dist/agent/commands/goal.d.ts +33 -1
- package/dist/agent/commands/goal.js +174 -14
- package/dist/agent/commands/goal_exit.d.ts +36 -0
- package/dist/agent/commands/goal_exit.js +120 -0
- package/dist/agent/commands/help.js +3 -1
- package/dist/agent/commands/history.js +3 -1
- package/dist/agent/commands/inbox.d.ts +2 -0
- package/dist/agent/commands/inbox.js +148 -0
- package/dist/agent/commands/index.js +16 -1
- package/dist/agent/commands/input_mode.js +8 -6
- package/dist/agent/commands/job.d.ts +2 -0
- package/dist/agent/commands/job.js +93 -0
- package/dist/agent/commands/keybindings.js +4 -1
- package/dist/agent/commands/label.d.ts +2 -0
- package/dist/agent/commands/label.js +77 -0
- package/dist/agent/commands/layout.d.ts +2 -0
- package/dist/agent/commands/layout.js +20 -0
- package/dist/agent/commands/mirror.js +16 -1
- package/dist/agent/commands/model.js +12 -2
- package/dist/agent/commands/permissions.d.ts +3 -1
- package/dist/agent/commands/permissions.js +79 -89
- package/dist/agent/commands/plan.js +5 -15
- package/dist/agent/commands/profile.js +21 -2
- package/dist/agent/commands/provider_selection.js +20 -1
- package/dist/agent/commands/rename.js +1 -1
- package/dist/agent/commands/resources.d.ts +2 -0
- package/dist/agent/commands/resources.js +67 -0
- package/dist/agent/commands/rewind.d.ts +3 -0
- package/dist/agent/commands/rewind.js +16 -0
- package/dist/agent/commands/runbooks.d.ts +2 -0
- package/dist/agent/commands/runbooks.js +73 -0
- package/dist/agent/commands/runbooks_lazy.d.ts +2 -0
- package/dist/agent/commands/runbooks_lazy.js +8 -0
- package/dist/agent/commands/sessions.d.ts +1 -1
- package/dist/agent/commands/sessions.js +33 -5
- package/dist/agent/commands/settings.js +185 -39
- package/dist/agent/commands/setup.d.ts +2 -0
- package/dist/agent/commands/setup.js +78 -0
- package/dist/agent/commands/share.d.ts +2 -0
- package/dist/agent/commands/share.js +64 -0
- package/dist/agent/commands/status.js +14 -2
- package/dist/agent/commands/types.d.ts +99 -6
- package/dist/agent/commands/update.js +8 -0
- package/dist/agent/commands/usage.d.ts +2 -0
- package/dist/agent/commands/usage.js +8 -0
- package/dist/agent/factory.d.ts +11 -0
- package/dist/agent/factory.js +113 -88
- package/dist/agent/loop_policy.d.ts +6 -3
- package/dist/agent/loop_policy.js +12 -3
- package/dist/agent/measurement_guidance.d.ts +2 -0
- package/dist/agent/measurement_guidance.js +5 -0
- package/dist/agent/request_encoder.d.ts +8 -1
- package/dist/agent/request_encoder.js +95 -23
- package/dist/agent/runtime/benchmark_comparison.d.ts +30 -0
- package/dist/agent/runtime/benchmark_comparison.js +94 -0
- package/dist/agent/runtime/benchmark_contract.d.ts +48 -0
- package/dist/agent/runtime/benchmark_contract.js +90 -0
- package/dist/agent/runtime/checked_commands.d.ts +32 -0
- package/dist/agent/runtime/checked_commands.js +119 -0
- package/dist/agent/runtime/command_execution_policy.d.ts +26 -0
- package/dist/agent/runtime/command_execution_policy.js +91 -0
- package/dist/agent/runtime/command_jobs.d.ts +36 -18
- package/dist/agent/runtime/command_jobs.js +151 -13
- package/dist/agent/runtime/command_panes.d.ts +59 -0
- package/dist/agent/runtime/command_panes.js +398 -0
- package/dist/agent/runtime/command_wait.d.ts +19 -0
- package/dist/agent/runtime/command_wait.js +167 -0
- package/dist/agent/runtime/cpu_benchmark.d.ts +30 -0
- package/dist/agent/runtime/cpu_benchmark.js +126 -0
- package/dist/agent/runtime/delegated_evidence.d.ts +26 -0
- package/dist/agent/runtime/delegated_evidence.js +80 -0
- package/dist/agent/runtime/evidence.d.ts +127 -0
- package/dist/agent/runtime/evidence.js +468 -0
- package/dist/agent/runtime/execution_context.d.ts +7 -0
- package/dist/agent/runtime/execution_context.js +39 -3
- package/dist/agent/runtime/json_verifier_recipe.d.ts +8 -0
- package/dist/agent/runtime/json_verifier_recipe.js +66 -0
- package/dist/agent/runtime/measurements.d.ts +110 -0
- package/dist/agent/runtime/measurements.js +317 -0
- package/dist/agent/runtime/policy.d.ts +4 -0
- package/dist/agent/runtime/policy.js +31 -20
- package/dist/agent/runtime/provider_native_evidence.d.ts +20 -0
- package/dist/agent/runtime/provider_native_evidence.js +86 -0
- package/dist/agent/runtime/recovery.d.ts +2 -0
- package/dist/agent/runtime/recovery.js +45 -2
- package/dist/agent/runtime/remote_broker_transport.d.ts +10 -0
- package/dist/agent/runtime/remote_broker_transport.js +84 -0
- package/dist/agent/runtime/remote_deletion_receipts.d.ts +3 -0
- package/dist/agent/runtime/remote_deletion_receipts.js +43 -0
- package/dist/agent/runtime/remote_journal_replay.d.ts +13 -0
- package/dist/agent/runtime/remote_journal_replay.js +233 -0
- package/dist/agent/runtime/remote_mutation_journal.d.ts +25 -0
- package/dist/agent/runtime/remote_mutation_journal.js +208 -0
- package/dist/agent/runtime/remote_resource_endpoint.d.ts +7 -0
- package/dist/agent/runtime/remote_resource_endpoint.js +6 -0
- package/dist/agent/runtime/remote_resource_errors.d.ts +5 -0
- package/dist/agent/runtime/remote_resource_errors.js +9 -0
- package/dist/agent/runtime/remote_resource_script.d.ts +2 -0
- package/dist/agent/runtime/remote_resource_script.js +1009 -0
- package/dist/agent/runtime/remote_resources.d.ts +29 -0
- package/dist/agent/runtime/remote_resources.js +160 -0
- package/dist/agent/runtime/source_verification.d.ts +12 -0
- package/dist/agent/runtime/source_verification.js +210 -0
- package/dist/agent/runtime/source_verification_contract.d.ts +75 -0
- package/dist/agent/runtime/source_verification_contract.js +203 -0
- package/dist/agent/runtime/tool_checkpoints.d.ts +11 -0
- package/dist/agent/runtime/tool_checkpoints.js +26 -0
- package/dist/agent/runtime/tool_effects.d.ts +5 -0
- package/dist/agent/runtime/tool_progress.d.ts +12 -0
- package/dist/agent/runtime/tool_progress.js +76 -0
- package/dist/agent/runtime/tool_runtime.d.ts +16 -1
- package/dist/agent/runtime/tool_runtime.js +204 -10
- package/dist/agent/runtime/tracing.d.ts +1 -1
- package/dist/agent/runtime/verifier_capture.d.ts +9 -0
- package/dist/agent/runtime/verifier_capture.js +100 -0
- package/dist/agent/runtime/verifier_contract.d.ts +24 -0
- package/dist/agent/runtime/verifier_contract.js +41 -0
- package/dist/agent/subagent.d.ts +20 -10
- package/dist/agent/subagent.js +266 -29
- package/dist/agent/tool_response_directory.d.ts +24 -0
- package/dist/agent/tool_response_directory.js +61 -0
- package/dist/agent/tool_response_source.d.ts +8 -0
- package/dist/agent/tool_response_source.js +49 -0
- package/dist/agent/tool_response_store.d.ts +75 -4
- package/dist/agent/tool_response_store.js +583 -64
- package/dist/agent/tools/ask_user.d.ts +3 -1
- package/dist/agent/tools/ask_user.js +30 -11
- package/dist/agent/tools/background.js +1 -0
- package/dist/agent/tools/benchmark_cpu.d.ts +2 -0
- package/dist/agent/tools/benchmark_cpu.js +102 -0
- package/dist/agent/tools/compare_benchmarks.d.ts +2 -0
- package/dist/agent/tools/compare_benchmarks.js +53 -0
- package/dist/agent/tools/deferred.js +1 -0
- package/dist/agent/tools/delegate.js +18 -2
- package/dist/agent/tools/helper_requirements.d.ts +7 -0
- package/dist/agent/tools/helper_requirements.js +19 -0
- package/dist/agent/tools/index.d.ts +1 -0
- package/dist/agent/tools/index.js +3 -0
- package/dist/agent/tools/measurement_report.d.ts +22 -0
- package/dist/agent/tools/measurement_report.js +190 -0
- package/dist/agent/tools/pane.d.ts +0 -7
- package/dist/agent/tools/pane.js +5 -4
- package/dist/agent/tools/project_context.d.ts +3 -0
- package/dist/agent/tools/project_context.js +45 -0
- package/dist/agent/tools/recall_tool_response.d.ts +3 -5
- package/dist/agent/tools/recall_tool_response.js +19 -29
- package/dist/agent/tools/registry.d.ts +1 -0
- package/dist/agent/tools/registry.js +23 -0
- package/dist/agent/tools/related_sessions.d.ts +2 -0
- package/dist/agent/tools/related_sessions.js +156 -0
- package/dist/agent/tools/routine.js +13 -3
- package/dist/agent/tools/session_evidence.d.ts +3 -0
- package/dist/agent/tools/session_evidence.js +72 -0
- package/dist/agent/tools/shell.d.ts +4 -3
- package/dist/agent/tools/shell.js +174 -47
- package/dist/agent/tools/tool_search.js +3 -2
- package/dist/agent/tools/types.d.ts +28 -0
- package/dist/agent/tools/verify_artifact.d.ts +2 -0
- package/dist/agent/tools/verify_artifact.js +60 -0
- package/dist/agent/tools/workflow.js +1 -0
- package/dist/app/activity.d.ts +6 -1
- package/dist/app/activity.js +81 -12
- package/dist/app/agent_runner.d.ts +6 -1
- package/dist/app/agent_runner.js +34 -19
- package/dist/app/command_context.js +106 -10
- package/dist/app/controller.d.ts +15 -4
- package/dist/app/controller.js +29 -3
- package/dist/app/escape_stop.d.ts +11 -0
- package/dist/app/escape_stop.js +15 -0
- package/dist/app/liveness.d.ts +6 -4
- package/dist/app/liveness.js +13 -3
- package/dist/app/model_catalog_cache.d.ts +5 -0
- package/dist/app/model_catalog_cache.js +138 -0
- package/dist/app/ordered_transcript.js +43 -4
- package/dist/app/prompt_turn.d.ts +14 -0
- package/dist/app/prompt_turn.js +53 -0
- package/dist/app/recap.d.ts +3 -0
- package/dist/app/recap.js +9 -3
- package/dist/app/restart.d.ts +3 -2
- package/dist/app/restart.js +22 -7
- package/dist/app/rewind.d.ts +3 -3
- package/dist/app/rewind.js +3 -3
- package/dist/app/selection_completion.d.ts +17 -2
- package/dist/app/selection_completion.js +84 -9
- package/dist/app/session_manager.d.ts +38 -6
- package/dist/app/session_manager.js +263 -60
- package/dist/app/session_messages.d.ts +3 -0
- package/dist/app/session_messages.js +60 -0
- package/dist/app/task_scope_router.d.ts +20 -0
- package/dist/app/task_scope_router.js +173 -0
- package/dist/app/transcript_window.d.ts +2 -0
- package/dist/app/transcript_window.js +4 -0
- package/dist/app/workspace_admission_details.d.ts +3 -0
- package/dist/app/workspace_admission_details.js +26 -0
- package/dist/app/workspace_layout_controller.d.ts +11 -0
- package/dist/app/workspace_layout_controller.js +88 -0
- package/dist/app/workspace_monitor.d.ts +1 -0
- package/dist/app/workspace_monitor.js +216 -0
- package/dist/app/workspace_presets.d.ts +29 -0
- package/dist/app/workspace_presets.js +192 -0
- package/dist/attention/queue.d.ts +37 -0
- package/dist/attention/queue.js +183 -0
- package/dist/bench/rf_shared.d.ts +2 -0
- package/dist/board/binding.d.ts +42 -0
- package/dist/board/binding.js +38 -0
- package/dist/board/client.d.ts +42 -0
- package/dist/board/client.js +60 -0
- package/dist/cli.js +1 -1
- package/dist/cli_args.js +19 -17
- package/dist/cli_main.js +110 -13
- package/dist/cli_ratatui.d.ts +183 -16
- package/dist/cli_ratatui.js +3989 -1255
- package/dist/components/ActivityLine.d.ts +3 -2
- package/dist/components/ActivityLine.js +2 -2
- package/dist/components/InputArea.d.ts +19 -1
- package/dist/components/InputArea.js +264 -190
- package/dist/components/MessageList.d.ts +2 -0
- package/dist/components/MessageList.js +3 -1
- package/dist/components/SingleMessage.d.ts +2 -0
- package/dist/components/SingleMessage.js +4 -2
- package/dist/components/StatusBar.d.ts +4 -2
- package/dist/components/StatusBar.js +3 -1
- package/dist/conductor_branch.js +3 -0
- package/dist/config/api_retry.d.ts +6 -1
- package/dist/config/api_retry.js +19 -1
- package/dist/config/defaults.d.ts +2 -1
- package/dist/config/defaults.js +9 -2
- package/dist/config/message_display.d.ts +1 -0
- package/dist/config/message_display.js +17 -2
- package/dist/config/permissions.d.ts +21 -0
- package/dist/config/permissions.js +89 -0
- package/dist/config/resolved.d.ts +2 -2
- package/dist/config/types.d.ts +32 -5
- package/dist/config.d.ts +38 -4
- package/dist/config.js +290 -42
- package/dist/context/project_intelligence.d.ts +44 -0
- package/dist/context/project_intelligence.js +246 -0
- package/dist/desktop/cli_launch.d.ts +8 -0
- package/dist/desktop/cli_launch.js +6 -0
- package/dist/desktop/cli_shim.d.ts +8 -0
- package/dist/desktop/cli_shim.js +120 -0
- package/dist/desktop/runtime.d.ts +16 -0
- package/dist/desktop/runtime.js +161 -0
- package/dist/desktop/worker_launch.d.ts +7 -0
- package/dist/desktop/worker_launch.js +7 -0
- package/dist/diagnostics/cargo_mutants_selection.d.ts +27 -0
- package/dist/diagnostics/cargo_mutants_selection.js +48 -0
- package/dist/diagnostics/native_terminal_benchmark.d.ts +58 -0
- package/dist/diagnostics/native_terminal_benchmark.js +1247 -0
- package/dist/diagnostics/session_evidence_package.d.ts +40 -0
- package/dist/diagnostics/session_evidence_package.js +234 -0
- package/dist/diagnostics/session_export.d.ts +93 -0
- package/dist/diagnostics/session_export.js +634 -0
- package/dist/emulation/tool_adapters.js +48 -18
- package/dist/emulation/ztc_addendum.d.ts +1 -0
- package/dist/emulation/ztc_addendum.js +13 -12
- package/dist/environment/service_runner.d.ts +1 -0
- package/dist/environment/service_runner.js +68 -0
- package/dist/environment/store.d.ts +120 -0
- package/dist/environment/store.js +387 -0
- package/dist/events.d.ts +40 -1
- package/dist/events.js +10 -17
- package/dist/goal_contract.d.ts +25 -0
- package/dist/goal_contract.js +44 -0
- package/dist/headless.d.ts +31 -0
- package/dist/headless.js +578 -70
- package/dist/headless_cli.js +8 -0
- package/dist/headless_cli_args.d.ts +1 -0
- package/dist/headless_cli_args.js +1 -0
- package/dist/intake/cli.d.ts +1 -0
- package/dist/intake/cli.js +33 -0
- package/dist/intake/format.d.ts +2 -0
- package/dist/intake/format.js +14 -0
- package/dist/intake/notifications.d.ts +16 -0
- package/dist/intake/notifications.js +132 -0
- package/dist/intake/store.d.ts +56 -0
- package/dist/intake/store.js +238 -0
- package/dist/library.d.ts +8 -0
- package/dist/library.js +19 -10
- package/dist/mcp/client.d.ts +1 -0
- package/dist/mcp/client.js +20 -0
- package/dist/modes/builtin/plan.yaml +2 -2
- package/dist/modes/handlers.js +1 -1
- package/dist/onboarding/first_run.d.ts +3 -0
- package/dist/onboarding/first_run.js +33 -25
- package/dist/onboarding/setup.d.ts +32 -0
- package/dist/onboarding/setup.js +133 -0
- package/dist/onboarding/setup_agent_health.d.ts +17 -0
- package/dist/onboarding/setup_agent_health.js +79 -0
- package/dist/onboarding/setup_environment.d.ts +66 -0
- package/dist/onboarding/setup_environment.js +219 -0
- package/dist/onboarding/setup_workspace.d.ts +17 -0
- package/dist/onboarding/setup_workspace.js +129 -0
- package/dist/optimize/adapters/index.d.ts +4 -1
- package/dist/optimize/adapters/index.js +7 -0
- package/dist/optimize/adapters/kernelbench.js +14 -5
- package/dist/optimize/adapters/popcorn.js +3 -2
- package/dist/optimize/adapters/profile_benchmark.js +13 -7
- package/dist/optimize/adapters/rfbench.js +25 -14
- package/dist/optimize/adapters/types.d.ts +3 -0
- package/dist/optimize/adapters/zergpod.js +5 -2
- package/dist/optimize/baseline.js +10 -21
- package/dist/optimize/branch.js +9 -12
- package/dist/optimize/combine.js +2 -3
- package/dist/optimize/executor.js +34 -48
- package/dist/optimize/measurements.d.ts +55 -0
- package/dist/optimize/measurements.js +173 -0
- package/dist/optimize/results.d.ts +1 -0
- package/dist/optimize/results.js +11 -33
- package/dist/optimize/tge.js +2 -1
- package/dist/panes/initial_input.d.ts +19 -0
- package/dist/panes/initial_input.js +206 -0
- package/dist/panes/labels.d.ts +16 -0
- package/dist/panes/labels.js +42 -0
- package/dist/panes/model_catalog.d.ts +3 -3
- package/dist/panes/model_catalog.js +28 -4
- package/dist/panes/pane_registry.js +6 -2
- package/dist/panes/types.d.ts +3 -0
- package/dist/panes/ztc_launch.js +19 -0
- package/dist/plugin/optimization.d.ts +2 -0
- package/dist/plugin/optimization.js +1 -0
- package/dist/plugin/runtime.d.ts +2 -0
- package/dist/plugin/simulation.d.ts +6 -1
- package/dist/plugin/simulation_contract.d.ts +836 -1
- package/dist/plugin/simulation_documents.d.ts +22 -0
- package/dist/plugin/simulation_documents.js +1 -0
- package/dist/plugin/simulation_interventions.d.ts +103 -0
- package/dist/plugin/simulation_interventions.js +1 -0
- package/dist/plugin/simulation_living.d.ts +1 -0
- package/dist/plugin/simulation_living.js +3 -0
- package/dist/plugin/simulation_meetings.d.ts +121 -0
- package/dist/plugin/simulation_meetings.js +1 -0
- package/dist/plugin/simulation_story_review.d.ts +111 -0
- package/dist/plugin/simulation_story_review.js +1 -0
- package/dist/plugin/simulation_storyteller.d.ts +91 -0
- package/dist/plugin/simulation_storyteller.js +1 -0
- package/dist/plugin/simulation_structured.d.ts +2 -0
- package/dist/plugin/simulation_structured.js +2 -0
- package/dist/plugin/simulation_toolkit.d.ts +21 -0
- package/dist/plugin/simulation_toolkit.js +24 -0
- package/dist/prompts/tool_delegate_task.j2 +1 -1
- package/dist/remote-broker/Dockerfile +10 -0
- package/dist/remote-broker/archive.py +1227 -0
- package/dist/remote-broker/cutover.py +761 -0
- package/dist/remote-broker/requirements.txt +5 -0
- package/dist/remote-broker/resources.json +6 -0
- package/dist/remote-broker/resources.write-staging.example.json +18 -0
- package/dist/remote-broker/retained.py +686 -0
- package/dist/remote-broker/retained_cleanup.py +550 -0
- package/dist/remote-broker/server.py +648 -0
- package/dist/remote-broker/worker.py +1007 -0
- package/dist/router_auth.d.ts +31 -0
- package/dist/router_auth.js +155 -9
- package/dist/runbooks/catalog.d.ts +3 -0
- package/dist/runbooks/catalog.js +26 -0
- package/dist/runbooks/library.d.ts +11 -0
- package/dist/runbooks/library.js +95 -0
- package/dist/runbooks/package.d.ts +43 -0
- package/dist/runbooks/package.js +102 -0
- package/dist/session.d.ts +65 -13
- package/dist/session.js +548 -43
- package/dist/session_handoff.d.ts +25 -0
- package/dist/session_handoff.js +322 -0
- package/dist/session_handoff_contract.d.ts +89 -0
- package/dist/session_handoff_contract.js +179 -0
- package/dist/session_media.d.ts +41 -0
- package/dist/session_media.js +357 -0
- package/dist/session_ownership.d.ts +16 -0
- package/dist/session_ownership.js +221 -0
- package/dist/session_public.d.ts +10 -2
- package/dist/session_public.js +6 -1
- package/dist/simulation/dynamic_history.d.ts +42 -0
- package/dist/simulation/dynamic_history.js +173 -0
- package/dist/simulation/engines/swarm/pane_manager.js +7 -6
- package/dist/simulation/inference.d.ts +3 -1
- package/dist/simulation/inference.js +19 -1
- package/dist/simulation/intervention_generation.d.ts +27 -0
- package/dist/simulation/intervention_generation.js +246 -0
- package/dist/simulation/intervention_material.d.ts +4 -0
- package/dist/simulation/intervention_material.js +81 -0
- package/dist/simulation/living/context.d.ts +3130 -0
- package/dist/simulation/living/context.js +944 -0
- package/dist/simulation/living/definer.d.ts +12 -0
- package/dist/simulation/living/definer.js +223 -0
- package/dist/simulation/living/definition.d.ts +257 -0
- package/dist/simulation/living/definition.js +341 -0
- package/dist/simulation/living/frame.d.ts +259 -0
- package/dist/simulation/living/frame.js +223 -0
- package/dist/simulation/living/identifiers.d.ts +2 -0
- package/dist/simulation/living/identifiers.js +12 -0
- package/dist/simulation/living/index.d.ts +15 -0
- package/dist/simulation/living/index.js +15 -0
- package/dist/simulation/living/inference.d.ts +219 -0
- package/dist/simulation/living/inference.js +576 -0
- package/dist/simulation/living/mechanics.d.ts +54 -0
- package/dist/simulation/living/mechanics.js +588 -0
- package/dist/simulation/living/model-input.d.ts +25 -0
- package/dist/simulation/living/model-input.js +215 -0
- package/dist/simulation/living/open-world.d.ts +257 -0
- package/dist/simulation/living/open-world.js +102 -0
- package/dist/simulation/living/projection.d.ts +7 -0
- package/dist/simulation/living/projection.js +31 -0
- package/dist/simulation/living/recall.d.ts +17 -0
- package/dist/simulation/living/recall.js +25 -0
- package/dist/simulation/living/reduce.d.ts +15 -0
- package/dist/simulation/living/reduce.js +1853 -0
- package/dist/simulation/living/replay.d.ts +6 -0
- package/dist/simulation/living/replay.js +22 -0
- package/dist/simulation/living/runtime.d.ts +140 -0
- package/dist/simulation/living/runtime.js +3020 -0
- package/dist/simulation/living/shared-kinds.d.ts +24 -0
- package/dist/simulation/living/shared-kinds.js +96 -0
- package/dist/simulation/living/types.d.ts +2785 -0
- package/dist/simulation/living/types.js +669 -0
- package/dist/simulation/llm.d.ts +3 -1
- package/dist/simulation/llm.js +90 -30
- package/dist/simulation/memory/embedding.d.ts +1 -1
- package/dist/simulation/memory/embedding.js +9 -3
- package/dist/simulation/memory/filesystem.d.ts +2 -0
- package/dist/simulation/memory/filesystem.js +5 -1
- package/dist/simulation/memory/filesystem_file_tree.d.ts +4 -1
- package/dist/simulation/memory/filesystem_file_tree.js +41 -10
- package/dist/simulation/memory/filesystem_invariants.js +1 -2
- package/dist/simulation/memory/filesystem_types.d.ts +1 -0
- package/dist/simulation/memory/memory.d.ts +2 -0
- package/dist/simulation/memory/memory.js +5 -1
- package/dist/simulation/orchestration/intent.js +0 -2
- package/dist/simulation/orchestration/lifecycle.js +0 -2
- package/dist/simulation/plugin_host.d.ts +25 -1
- package/dist/simulation/plugin_host.js +334 -17
- package/dist/simulation/session_documents.d.ts +20 -0
- package/dist/simulation/session_documents.js +62 -0
- package/dist/simulation/session_input_validation.d.ts +43 -0
- package/dist/simulation/session_input_validation.js +364 -0
- package/dist/simulation/session_inputs.d.ts +32 -0
- package/dist/simulation/session_inputs.js +154 -0
- package/dist/simulation/telemetry.d.ts +11 -14
- package/dist/simulation/telemetry.js +177 -54
- package/dist/simulation/toolkit/bounded_turns.d.ts +13 -0
- package/dist/simulation/toolkit/bounded_turns.js +80 -0
- package/dist/simulation/toolkit/dynamic_agents.d.ts +25 -0
- package/dist/simulation/toolkit/dynamic_agents.js +218 -0
- package/dist/simulation/toolkit/dynamic_digest.d.ts +1 -0
- package/dist/simulation/toolkit/dynamic_digest.js +8 -0
- package/dist/simulation/toolkit/dynamic_documents.d.ts +7 -0
- package/dist/simulation/toolkit/dynamic_documents.js +71 -0
- package/dist/simulation/toolkit/dynamic_driver.d.ts +8 -0
- package/dist/simulation/toolkit/dynamic_driver.js +269 -0
- package/dist/simulation/toolkit/dynamic_game.d.ts +53 -0
- package/dist/simulation/toolkit/dynamic_game.js +805 -0
- package/dist/simulation/toolkit/dynamic_knowledge.d.ts +15 -0
- package/dist/simulation/toolkit/dynamic_knowledge.js +91 -0
- package/dist/simulation/toolkit/dynamic_mechanics.d.ts +22 -0
- package/dist/simulation/toolkit/dynamic_mechanics.js +910 -0
- package/dist/simulation/toolkit/dynamic_meetings.d.ts +239 -0
- package/dist/simulation/toolkit/dynamic_meetings.js +350 -0
- package/dist/simulation/toolkit/dynamic_progress.d.ts +131 -0
- package/dist/simulation/toolkit/dynamic_progress.js +214 -0
- package/dist/simulation/toolkit/dynamic_projection.d.ts +9 -0
- package/dist/simulation/toolkit/dynamic_projection.js +219 -0
- package/dist/simulation/toolkit/dynamic_runtime.d.ts +412 -0
- package/dist/simulation/toolkit/dynamic_runtime.js +6536 -0
- package/dist/simulation/toolkit/dynamic_state.d.ts +111 -0
- package/dist/simulation/toolkit/dynamic_state.js +1967 -0
- package/dist/simulation/toolkit/dynamic_story_review.d.ts +508 -0
- package/dist/simulation/toolkit/dynamic_story_review.js +333 -0
- package/dist/simulation/toolkit/dynamic_storyteller.d.ts +77 -0
- package/dist/simulation/toolkit/dynamic_storyteller.js +114 -0
- package/dist/simulation/toolkit/dynamic_storyteller_runtime.d.ts +26 -0
- package/dist/simulation/toolkit/dynamic_storyteller_runtime.js +126 -0
- package/dist/simulation/toolkit/dynamic_world.d.ts +22 -0
- package/dist/simulation/toolkit/dynamic_world.js +377 -0
- package/dist/simulation/toolkit/structured_output.d.ts +5 -0
- package/dist/simulation/toolkit/structured_output.js +321 -0
- package/dist/simulation/types/world.d.ts +2 -0
- package/dist/simulation/world/artifact_store.js +1 -1
- package/dist/simulation/world/memory_manager.d.ts +5 -0
- package/dist/simulation/world/memory_manager.js +17 -2
- package/dist/simulation/world/state.d.ts +3 -2
- package/dist/simulation/world/state.js +15 -5
- package/dist/skills/bundled/measurement-validation/SKILL.md +144 -0
- package/dist/terminal_attach.d.ts +1 -0
- package/dist/terminal_attach.js +113 -0
- package/dist/tool_progress.d.ts +3 -0
- package/dist/tool_progress.js +27 -0
- package/dist/types.d.ts +145 -10
- package/dist/types.js +3 -0
- package/dist/ui/core/input.d.ts +3 -0
- package/dist/ui/core/input_segments.d.ts +18 -1
- package/dist/ui/core/input_segments.js +131 -47
- package/dist/ui/core/input_state.d.ts +12 -0
- package/dist/ui/core/layout_yoga.d.ts +1 -1
- package/dist/ui/core/turn_metadata.d.ts +5 -0
- package/dist/ui/core/turn_metadata.js +22 -0
- package/dist/ui/ink/render.d.ts +2 -1
- package/dist/ui/ink/render.js +6 -5
- package/dist/ui/ratatui/headless_pane_manager.d.ts +14 -1
- package/dist/ui/ratatui/headless_pane_manager.js +293 -104
- package/dist/ui/ratatui/index.d.ts +1 -0
- package/dist/ui/ratatui/input.d.ts +4 -0
- package/dist/ui/ratatui/input.js +15 -0
- package/dist/ui/ratatui/ipc_path.d.ts +2 -0
- package/dist/ui/ratatui/ipc_path.js +29 -1
- package/dist/ui/ratatui/mux_persistence.d.ts +3 -1
- package/dist/ui/ratatui/mux_persistence.js +139 -1
- package/dist/ui/ratatui/pane_relay.d.ts +1 -1
- package/dist/ui/ratatui/pane_relay.js +55 -27
- package/dist/ui/ratatui/process.d.ts +17 -1
- package/dist/ui/ratatui/process.js +80 -7
- package/dist/ui/ratatui/protocol.d.ts +236 -1
- package/dist/ui/ratatui/protocol.js +90 -0
- package/dist/ui/scrollback.d.ts +2 -1
- package/dist/ui/scrollback.js +9 -4
- package/dist/ui/stream_typewriter.d.ts +8 -3
- package/dist/ui/stream_typewriter.js +30 -1
- package/dist/ui/streaming_scrollback.d.ts +1 -0
- package/dist/ui/streaming_scrollback.js +1 -1
- package/dist/ui/transcript_render_cache.js +10 -0
- package/dist/ui/views/activity_line.d.ts +5 -3
- package/dist/ui/views/activity_line.js +65 -7
- package/dist/ui/views/app.d.ts +6 -3
- package/dist/ui/views/app.js +17 -6
- package/dist/ui/views/completion_menu.js +10 -5
- package/dist/ui/views/input_area.d.ts +7 -3
- package/dist/ui/views/input_area.js +15 -9
- package/dist/ui/views/message_list.d.ts +4 -1
- package/dist/ui/views/message_list.js +89 -19
- package/dist/ui/views/queue_panel.d.ts +3 -1
- package/dist/ui/views/queue_panel.js +9 -2
- package/dist/ui/views/status_bar.d.ts +4 -2
- package/dist/ui/views/status_bar.js +28 -13
- package/dist/ui/views/theme.d.ts +2 -0
- package/dist/ui/views/theme.js +2 -0
- package/dist/ui/widgets/color_panel.js +18 -0
- package/dist/ui/widgets/form.js +8 -0
- package/dist/ui/widgets/log_view.js +7 -0
- package/dist/ui/widgets/navigator_panel.js +8 -0
- package/dist/ui/widgets/prompt_viewer.js +2 -0
- package/dist/ui/widgets/questionnaire.d.ts +2 -0
- package/dist/ui/widgets/questionnaire.js +79 -16
- package/dist/ui/widgets/resume_panel.d.ts +2 -0
- package/dist/ui/widgets/resume_panel.js +26 -15
- package/dist/ui/widgets/rewind_panel.d.ts +2 -2
- package/dist/ui/widgets/rewind_panel.js +4 -2
- package/dist/ui/widgets/select.js +16 -27
- package/dist/ui/widgets/settings_panel.js +16 -0
- package/dist/ui/widgets/status_line_panel.d.ts +1 -0
- package/dist/ui/widgets/status_line_panel.js +30 -4
- package/dist/ui/widgets/text_input.d.ts +2 -0
- package/dist/ui/widgets/text_input.js +15 -1
- package/dist/ui/widgets/types.d.ts +2 -0
- package/dist/usage_projection.d.ts +24 -0
- package/dist/usage_projection.js +130 -0
- package/dist/usage_snapshot.d.ts +36 -0
- package/dist/usage_snapshot.js +131 -0
- package/dist/utils/assistant_message.d.ts +2 -0
- package/dist/utils/assistant_message.js +5 -0
- package/dist/utils/atomic_write.d.ts +6 -2
- package/dist/utils/atomic_write.js +29 -4
- package/dist/utils/checked_shell.d.ts +25 -0
- package/dist/utils/checked_shell.js +107 -0
- package/dist/utils/compaction.d.ts +4 -1
- package/dist/utils/compaction.js +31 -7
- package/dist/utils/context.d.ts +4 -4
- package/dist/utils/context.js +14 -12
- package/dist/utils/deepseek.d.ts +2 -2
- package/dist/utils/deepseek.js +13 -5
- package/dist/utils/ephemeral.js +4 -0
- package/dist/utils/ephemeral_tools.d.ts +18 -0
- package/dist/utils/ephemeral_tools.js +97 -0
- package/dist/utils/jev_client.d.ts +4 -0
- package/dist/utils/jev_client.js +15 -0
- package/dist/utils/model_cache.d.ts +15 -0
- package/dist/utils/model_cache.js +143 -0
- package/dist/utils/models.d.ts +4 -2
- package/dist/utils/models.js +12 -16
- package/dist/utils/reasoning.d.ts +8 -1
- package/dist/utils/reasoning.js +31 -11
- package/dist/utils/router_catalog.d.ts +49 -0
- package/dist/utils/router_catalog.js +271 -0
- package/dist/utils/sandbox.d.ts +9 -0
- package/dist/utils/sandbox.js +118 -15
- package/dist/utils/shell.d.ts +7 -0
- package/dist/utils/shell.js +98 -34
- package/dist/utils/subprocess_env.d.ts +3 -0
- package/dist/utils/subprocess_env.js +27 -1
- package/dist/web/accounts_mirror.d.ts +3 -1
- package/dist/web/accounts_mirror.js +13 -9
- package/dist/web/child_publication.d.ts +17 -0
- package/dist/web/child_publication.js +45 -0
- package/dist/web/mirror_authenticator.d.ts +31 -11
- package/dist/web/mirror_authenticator.js +67 -17
- package/dist/web/mirror_client.d.ts +31 -4
- package/dist/web/mirror_client.js +189 -23
- package/dist/web/mirror_stream.d.ts +84 -22
- package/dist/web/mirror_stream.js +676 -95
- package/dist/web/observer_query_redaction.d.ts +6 -0
- package/dist/web/observer_query_redaction.js +58 -0
- package/dist/web/pane_registry.d.ts +2 -0
- package/dist/web/pane_registry.js +86 -22
- package/dist/web/tool_image.d.ts +6 -0
- package/dist/web/tool_image.js +17 -0
- package/dist/web/view_plugin.d.ts +14 -0
- package/dist/web/view_plugin.js +23 -1
- package/dist/worker/executor.js +31 -12
- package/dist/worker/index.d.ts +1 -0
- package/dist/worker/toolsets.js +3 -1
- package/dist/worker/types.d.ts +12 -1
- package/dist/workflows/registry.js +1 -0
- package/dist/workflows/registry_handle.d.ts +2 -0
- package/dist/workflows/runtime.d.ts +2 -0
- package/dist/workflows/runtime.js +1 -0
- package/dist/workspace_agent.d.ts +7 -0
- package/dist/workspace_agent.js +36 -0
- package/dist/workspace_collision.d.ts +76 -0
- package/dist/workspace_collision.js +688 -0
- package/dist/workspace_layout.d.ts +18 -0
- package/dist/workspace_layout.js +121 -0
- package/dist/workspace_runtime.d.ts +44 -0
- package/dist/workspace_runtime.js +82 -0
- package/dist/workspace_session_context.d.ts +62 -0
- package/dist/workspace_session_context.js +160 -0
- package/dist/zlm-embed.mjs +32518 -9615
- package/dist/zlm.js +1 -0
- package/node_modules/@zerg/model-contracts/LICENSE.md +375 -0
- package/node_modules/@zerg/model-contracts/README.md +90 -0
- package/node_modules/@zerg/model-contracts/index.d.ts +20 -0
- package/node_modules/@zerg/model-contracts/index.js +223 -0
- package/node_modules/@zerg/model-contracts/package.json +8 -0
- package/node_modules/glob/node_modules/brace-expansion/index.js +78 -22
- package/node_modules/glob/node_modules/brace-expansion/package.json +1 -1
- package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
- package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
- package/package.json +45 -24
- package/packages/model-contracts/LICENSE.md +375 -0
- package/packages/model-contracts/README.md +90 -0
- package/packages/model-contracts/index.d.ts +20 -0
- package/packages/model-contracts/index.js +223 -0
- package/packages/model-contracts/package.json +8 -0
- package/remote-broker/Dockerfile +10 -0
- package/remote-broker/README.md +308 -0
- package/remote-broker/archive.py +1227 -0
- package/remote-broker/cutover.py +761 -0
- package/remote-broker/requirements.txt +5 -0
- package/remote-broker/resources.example.json +6 -0
- package/remote-broker/resources.write-staging.example.json +18 -0
- package/remote-broker/retained.py +686 -0
- package/remote-broker/retained_cleanup.py +550 -0
- package/remote-broker/server.py +648 -0
- package/scripts/build-remote-broker.mjs +13 -0
- package/scripts/ensure-local-packages.mjs +6 -0
- package/scripts/node-pty-helpers.mjs +103 -0
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
import { compactModelInput, decisionInputBudget, MODEL_VIEW_NOTE, selectModelPeople, syncModelPeople } from './model-input.js';
|
|
2
|
+
import { recallBeliefs, recallMemories } from './recall.js';
|
|
3
|
+
import { attemptResponseSchema, epilogueResponseSchema, messageReplyResponseSchema, planResponseSchema, reactionResponseSchema, responseFieldLines, speechImpactResponseSchema } from './inference.js';
|
|
4
|
+
import { blockedActionKinds, currentValue, placelessNear } from './mechanics.js';
|
|
5
|
+
import { peopleAt, unansweredSince, votingShut } from './reduce.js';
|
|
6
|
+
import { knowsCustody, observedSummary, visibleTo, witnessed } from './projection.js';
|
|
7
|
+
import { SHARED_KINDS, sealedPaper, staysInPlace } from './shared-kinds.js';
|
|
8
|
+
import { CHRONICLE_ENGINE, IDENTITY_ENGINE, LONG_DAY_MINUTES, PLAYER_QUIET_MINUTES, PLAYER_REST_ENGINE, SEEN_AT_EVENTS_ENGINE, WHOLE_TEXT_ENGINE, WRITING_GROWS_ENGINE, clockLabel, isNight } from './types.js';
|
|
9
|
+
// Context builders turn state into what one participant can know right now.
|
|
10
|
+
// The Game Master sees the world. A character sees their own mind and their scene.
|
|
11
|
+
// A serious thing stays pressing for this long: people do not go back to routine
|
|
12
|
+
// business within minutes of violence, fire, theft or a collapse.
|
|
13
|
+
export const PRESSING_MINUTES = 240;
|
|
14
|
+
export const PRESSING_IMPORTANCE = 6;
|
|
15
|
+
// Memories this grave stay in a person's mind for good, the gravest few of them.
|
|
16
|
+
const LANDMARK_IMPORTANCE = 8;
|
|
17
|
+
const LANDMARKS = 4;
|
|
18
|
+
// Each model call sees a bounded, relevant slice of a person's growing lists.
|
|
19
|
+
export const MIND_SLICE = 8;
|
|
20
|
+
// The newest things at a place that a scene lists, before the input budget trims it.
|
|
21
|
+
export const SCENE_SLICE = 32;
|
|
22
|
+
const REQUEST_SLICE = 12;
|
|
23
|
+
// Refused and open requests stay in view before settled ones, newest first within each.
|
|
24
|
+
function relevantRequests(requests) {
|
|
25
|
+
const rank = (request) => request.status === 'refused' || request.status === 'open' ? 0 : 1;
|
|
26
|
+
return [...requests].sort((left, right) => rank(left) - rank(right) || right.openedSeq - left.openedSeq)
|
|
27
|
+
.slice(0, REQUEST_SLICE).sort((left, right) => left.openedSeq - right.openedSeq);
|
|
28
|
+
}
|
|
29
|
+
export { clockLabel };
|
|
30
|
+
export function personLabel(state, personId) {
|
|
31
|
+
return state.people[personId]?.name ?? personId;
|
|
32
|
+
}
|
|
33
|
+
function placeLabel(state, placeId) {
|
|
34
|
+
return placeId === null ? null : state.places[placeId]?.name ?? placeId;
|
|
35
|
+
}
|
|
36
|
+
function personCard(state, id) {
|
|
37
|
+
const person = state.people[id];
|
|
38
|
+
// What anyone present can see the person doing; their purpose stays private. Someone whose next activity
|
|
39
|
+
// is somewhere else is seen getting ready to leave, not doing what they mean to do there.
|
|
40
|
+
const plan = person.plan;
|
|
41
|
+
const elsewhere = state.engine >= IDENTITY_ENGINE && plan?.placeId !== undefined && plan.placeId !== null && plan.placeId !== person.placeId;
|
|
42
|
+
// The player's person, two quiet game hours after the player last acted, is seen resting, or asleep at
|
|
43
|
+
// night; never while a long act of theirs is under way.
|
|
44
|
+
const resting = state.engine >= PLAYER_REST_ENGINE && person.isPlayer && state.clock - (state.playerActedAt ?? state.startClock ?? 0) >= PLAYER_QUIET_MINUTES
|
|
45
|
+
&& !state.schedule.some(item => item.kind === 'finish' && item.targetId === person.id)
|
|
46
|
+
? isNight(state.clock) ? 'asleep where they sit' : 'resting quietly, not taking part' : null;
|
|
47
|
+
return { id, name: person.name, role: person.role, pronouns: person.pronouns ?? 'they/them', summary: person.summary,
|
|
48
|
+
...(person.arrivedAt === undefined ? {} : { arrived: clockLabel(person.arrivedAt) }),
|
|
49
|
+
mood: null, condition: person.condition ?? null, doing: person.incapacitated ? null : resting ?? (elsewhere ? 'getting ready to leave' : plan?.seen ?? null),
|
|
50
|
+
held: null, incapacitated: person.incapacitated };
|
|
51
|
+
}
|
|
52
|
+
export function describeScene(state, placeId, viewerId, kept = new Set()) {
|
|
53
|
+
const place = state.places[placeId];
|
|
54
|
+
const people = peopleAt(state, placeId).filter(id => id !== viewerId).map((id) => {
|
|
55
|
+
const holding = [...Object.values(state.objects).filter(object => object.holderId === id
|
|
56
|
+
&& (viewerId === null || knowsCustody(state, object, viewerId))).map(object => object.name),
|
|
57
|
+
...(state.entityPlaceIds[placeId] ?? []).filter(entityId => state.entities[entityId].holderId === id
|
|
58
|
+
&& (viewerId === null || state.entities[entityId].knownTo.includes(viewerId))).map(entityId => state.entities[entityId].name)];
|
|
59
|
+
return { ...personCard(state, id), holding };
|
|
60
|
+
});
|
|
61
|
+
// A thing the world hides is seen only by those who know where it is.
|
|
62
|
+
const objects = Object.values(state.objects).filter(object => object.placeId === placeId
|
|
63
|
+
&& (!object.hidden || viewerId === null || object.holderId === viewerId || object.custodyKnownTo.includes(viewerId))).map(object => ({
|
|
64
|
+
id: object.id, name: object.name, summary: viewerId !== null && object.holderId !== viewerId && !object.conditionKnownTo.includes(viewerId) ? 'An object here.' : object.summary,
|
|
65
|
+
condition: viewerId !== null && object.holderId !== viewerId && !object.conditionKnownTo.includes(viewerId) ? 'unexamined' : object.condition,
|
|
66
|
+
singlePersonMovable: object.singlePersonMovable,
|
|
67
|
+
ownerName: viewerId !== null && object.ownerId !== viewerId ? null : object.ownerId ? personLabel(state, object.ownerId) : null,
|
|
68
|
+
...(viewerId === null && state.thingNotes[object.id] ? { refereeOnly: note(state.thingNotes[object.id]) } : {}),
|
|
69
|
+
}));
|
|
70
|
+
const near = new Set([placeId, ...peopleAt(state, placeId), ...(state.entityPlaceIds[placeId] ?? []).slice(-SCENE_SLICE),
|
|
71
|
+
...Object.values(state.objects).filter(object => object.placeId === placeId).map(object => object.id), ...(viewerId === null ? [] : [viewerId])]);
|
|
72
|
+
// A scene lists the newest things at the place; the input budget trims further.
|
|
73
|
+
const here = state.entityPlaceIds[placeId] ?? [];
|
|
74
|
+
// What someone holds or just dealt with is listed however many newer things lie about.
|
|
75
|
+
const newest = [...here.filter(id => kept.has(id) && !here.slice(-SCENE_SLICE).includes(id)), ...here.slice(-SCENE_SLICE)];
|
|
76
|
+
// From engine 50 what lies in the open where the viewer stands is in view, and how it plainly is.
|
|
77
|
+
const standsHere = viewerId !== null && state.engine >= SEEN_AT_EVENTS_ENGINE && state.people[viewerId]?.placeId === placeId
|
|
78
|
+
&& state.people[viewerId]?.away === undefined;
|
|
79
|
+
const inView = (entity) => standsHere && entity.placeId === placeId && entity.holderId === null && !entity.hidden;
|
|
80
|
+
const entities = [...placelessNear(state, near, MIND_SLICE), ...newest].map(id => state.entities[id])
|
|
81
|
+
.filter(entity => (viewerId === null || entity.knownTo.includes(viewerId) || inView(entity)) && !(entity.holderId && (state.people[entity.holderId]?.departed || state.people[entity.holderId]?.away)))
|
|
82
|
+
.map(entity => ({
|
|
83
|
+
id: entity.id, kindId: entity.kindId, kindName: state.kinds[entity.kindId].name, name: entity.name,
|
|
84
|
+
// A thing that is here can be picked up; who holds it is plain to see.
|
|
85
|
+
...(entity.placeId === placeId ? { holderId: entity.holderId, ...(entity.condition ? { condition: entity.condition } : {}) } : {}),
|
|
86
|
+
// Who votes on a decision, as anyone who sees it knows.
|
|
87
|
+
...(entity.members ? { members: entity.members } : {}),
|
|
88
|
+
// A sealed paper's words stay hidden from everyone in the world until it is opened.
|
|
89
|
+
properties: Object.fromEntries(Object.entries(entity.properties)
|
|
90
|
+
.filter(([key, slot]) => viewerId === null || (slot.knownTo.includes(viewerId) || inView(entity) && state.kinds[entity.kindId]?.properties.find(property => property.id === key)?.type !== 'text')
|
|
91
|
+
&& !(sealedPaper(state.engine, entity) && state.kinds[entity.kindId]?.properties.find(property => property.id === key)?.type === 'text'))
|
|
92
|
+
.map(([key]) => [key, currentValue(state, entity, key, state.clock)])),
|
|
93
|
+
relations: Object.fromEntries(Object.entries(entity.relations)
|
|
94
|
+
.filter(([, slot]) => viewerId === null || slot.knownTo.includes(viewerId))
|
|
95
|
+
.map(([key, slot]) => [key, slot.targetId])),
|
|
96
|
+
// Only the Game Master reads what the last ruling did with it.
|
|
97
|
+
...(viewerId === null && state.thingNotes[entity.id] ? { refereeOnly: note(state.thingNotes[entity.id]) } : {}),
|
|
98
|
+
}));
|
|
99
|
+
const exits = place.connections.map(id => ({ id, name: state.places[id]?.name ?? id }));
|
|
100
|
+
return { id: place.id, name: place.name, kind: place.kind, summary: place.summary, conditions: [...place.conditions], people, objects, entities, exits };
|
|
101
|
+
}
|
|
102
|
+
function recentSeqsAt(state, placeId, limit) {
|
|
103
|
+
return new Set(state.events.filter(event => event.placeId === placeId).slice(-limit).map(event => event.seq));
|
|
104
|
+
}
|
|
105
|
+
export function recentEventsAt(state, placeId, limit, viewerId, exceptSeq = null) {
|
|
106
|
+
return state.events
|
|
107
|
+
.filter(event => event.placeId === placeId && event.seq !== exceptSeq && (viewerId === null || witnessed(event, viewerId)))
|
|
108
|
+
.slice(-limit)
|
|
109
|
+
.map(event => ({ at: clockLabel(event.at), summary: viewerId === null ? event.summary : observedSummary(event, viewerId) }));
|
|
110
|
+
}
|
|
111
|
+
// The serious things that happened lately anywhere, so the world stays consistent
|
|
112
|
+
// about them (security already called, a fire still burning, someone hurt).
|
|
113
|
+
export function recentImportant(state, limit, shownHere = new Set()) {
|
|
114
|
+
// What was ruled to have happened, not what was said about it: talk stays with those who heard it.
|
|
115
|
+
return state.events
|
|
116
|
+
.filter(event => event.importance >= PRESSING_IMPORTANCE && (event.kind === 'attempt' || event.kind === 'world') && !shownHere.has(event.seq))
|
|
117
|
+
.slice(-limit)
|
|
118
|
+
.map(event => ({ at: clockLabel(event.at), place: placeLabel(state, event.placeId), summary: event.summary, witnesses: event.witnessIds.map(id => personLabel(state, id)) }));
|
|
119
|
+
}
|
|
120
|
+
export function openConversationAt(state, placeId, participantId) {
|
|
121
|
+
return state.conversations.find(conversation => conversation.endedAt === null && conversation.placeId === placeId && conversation.participantIds.includes(participantId)) ?? null;
|
|
122
|
+
}
|
|
123
|
+
// The event at which this person last walked into the place; lines said before that were not heard by them.
|
|
124
|
+
export function joinedPlaceSeq(state, personId, placeId) {
|
|
125
|
+
const person = state.people[personId];
|
|
126
|
+
if (person?.placeId === placeId && person.arrivedSeq !== undefined)
|
|
127
|
+
return person.arrivedSeq;
|
|
128
|
+
for (let index = state.events.length - 1; index >= 0; index -= 1) {
|
|
129
|
+
const event = state.events[index];
|
|
130
|
+
if (event.effects.some(effect => effect.kind === 'move' && effect.personId === personId && effect.placeId === placeId))
|
|
131
|
+
return event.seq;
|
|
132
|
+
}
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
// What this person said lately, anywhere: the lines they must not simply say again.
|
|
136
|
+
export const RECENT_LINES_MINUTES = 180;
|
|
137
|
+
export function recentLinesBy(state, personId, limit) {
|
|
138
|
+
return state.conversations
|
|
139
|
+
.flatMap(conversation => conversation.lines.filter(line => line.speakerId === personId && line.at >= state.clock - RECENT_LINES_MINUTES))
|
|
140
|
+
.sort((a, b) => a.seq - b.seq)
|
|
141
|
+
.slice(-limit);
|
|
142
|
+
}
|
|
143
|
+
function heardLines(state, placeId, participantId, limit) {
|
|
144
|
+
const conversation = openConversationAt(state, placeId, participantId);
|
|
145
|
+
if (!conversation)
|
|
146
|
+
return [];
|
|
147
|
+
const joined = joinedPlaceSeq(state, participantId, placeId);
|
|
148
|
+
return conversation.lines.filter(line => line.seq >= joined).slice(-limit);
|
|
149
|
+
}
|
|
150
|
+
function lineCard(state, line) {
|
|
151
|
+
return { speaker: personLabel(state, line.speakerId), speakerId: line.speakerId, to: line.toIds.map(id => personLabel(state, id)), text: line.text };
|
|
152
|
+
}
|
|
153
|
+
export function conversationLines(state, placeId, participantId, limit) {
|
|
154
|
+
return heardLines(state, placeId, participantId, limit).map(line => lineCard(state, line));
|
|
155
|
+
}
|
|
156
|
+
function note(entry) {
|
|
157
|
+
return { when: clockLabel(entry.at), what: entry.text };
|
|
158
|
+
}
|
|
159
|
+
export function worldDirectory(state, placeId) {
|
|
160
|
+
const localPlaces = new Set([placeId, ...state.places[placeId].connections]);
|
|
161
|
+
const localPeople = new Set(Object.values(state.people).filter(person => localPlaces.has(person.placeId) && person.away === undefined).map(person => person.id));
|
|
162
|
+
const away = Object.values(state.people).filter(person => localPlaces.has(person.placeId) && person.away !== undefined);
|
|
163
|
+
return {
|
|
164
|
+
places: Object.values(state.places).filter(place => localPlaces.has(place.id)).map(place => ({ id: place.id, name: place.name, kind: place.kind, connections: place.connections, conditions: [...place.conditions] })),
|
|
165
|
+
people: Object.values(state.people).filter(person => localPeople.has(person.id)).map(person => ({ id: person.id, name: person.name, role: person.role, pronouns: person.pronouns ?? 'they/them', placeId: person.placeId, isPlayer: person.isPlayer, incapacitated: person.incapacitated })),
|
|
166
|
+
// People from nearby who are off for a while, and where.
|
|
167
|
+
...(away.length === 0 ? {} : { away: away.map(person => ({ id: person.id, name: person.name, where: person.away.reason })) }),
|
|
168
|
+
// Everyone else in the story, by name, so no one is brought in a second time.
|
|
169
|
+
elsewhere: Object.values(state.people).filter(person => !localPlaces.has(person.placeId) && person.departed === undefined).map(person => ({ id: person.id, name: person.name })),
|
|
170
|
+
// Things at this place are already in the scene; the directory lists the rest nearby and what people hold.
|
|
171
|
+
objects: Object.values(state.objects).filter(object => (object.placeId !== null && object.placeId !== placeId && localPlaces.has(object.placeId)) || (object.holderId !== null && localPeople.has(object.holderId)))
|
|
172
|
+
.map(object => ({ id: object.id, name: object.name, placeId: object.placeId, holderId: object.holderId, ownerId: object.ownerId, condition: object.condition, singlePersonMovable: object.singlePersonMovable })),
|
|
173
|
+
tensions: state.tensions.filter(tension => tension.status === 'open' && tension.participantIds.some(id => localPeople.has(id)))
|
|
174
|
+
.slice(-MIND_SLICE).map(tension => ({ id: tension.id, title: tension.title, summary: tension.summary, ...(tension.openedBy === undefined ? {} : { openedBy: tension.openedBy }),
|
|
175
|
+
participants: tension.participantIds.map(id => ({ id, name: personLabel(state, id) })) })),
|
|
176
|
+
facts: state.facts.slice(-8),
|
|
177
|
+
...(state.startClock === undefined ? {} : { factsAsOf: clockLabel(state.startClock) }),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// A world begun on engines 42 to 44 adds each write to a paper as a line of its own, so its referee is told so.
|
|
181
|
+
function writeRule(state) {
|
|
182
|
+
return state.engine >= WRITING_GROWS_ENGINE && state.engine < WHOLE_TEXT_ENGINE ? { textWrites: 'add' } : {};
|
|
183
|
+
}
|
|
184
|
+
function kindsInScope(state, placeId) {
|
|
185
|
+
const nearby = new Set([placeId, ...state.places[placeId].connections]);
|
|
186
|
+
const ids = new Set(SHARED_KINDS.map(kind => kind.id));
|
|
187
|
+
const near = new Set([...nearby, ...Object.values(state.people).filter(person => nearby.has(person.placeId)).map(person => person.id)]);
|
|
188
|
+
// Kinds of things here carry their rules; the rest are named and described, which is all a new thing needs.
|
|
189
|
+
const inView = new Set();
|
|
190
|
+
for (const entityId of placelessNear(state, near, MIND_SLICE)) {
|
|
191
|
+
ids.add(state.entities[entityId].kindId);
|
|
192
|
+
inView.add(state.entities[entityId].kindId);
|
|
193
|
+
}
|
|
194
|
+
for (const nearbyPlaceId of nearby)
|
|
195
|
+
for (const entityId of (state.entityPlaceIds[nearbyPlaceId] ?? []).slice(-SCENE_SLICE)) {
|
|
196
|
+
ids.add(state.entities[entityId].kindId);
|
|
197
|
+
if (nearbyPlaceId === placeId)
|
|
198
|
+
inView.add(state.entities[entityId].kindId);
|
|
199
|
+
}
|
|
200
|
+
for (const event of state.events.slice(-8)) {
|
|
201
|
+
for (const effect of event.effects)
|
|
202
|
+
if (effect.kind === 'kind.define') {
|
|
203
|
+
ids.add(effect.definition.id);
|
|
204
|
+
inView.add(effect.definition.id);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return [...ids].sort().map(id => state.kinds[id]).filter((kind) => kind !== undefined)
|
|
208
|
+
.map(kind => state.engine < IDENTITY_ENGINE || inView.has(kind.id) ? kind : { ...kind, mechanics: [], ...(staysInPlace(kind) ? { neverHeld: true } : {}) });
|
|
209
|
+
}
|
|
210
|
+
// The Game Master adjudicates against the whole world, so it sees the directory.
|
|
211
|
+
export function gameMasterAttemptInput(state, actorId, attempt, previousErrors = [], _fitted = true, finishing, focusIds = []) {
|
|
212
|
+
const actor = state.people[actorId];
|
|
213
|
+
const close = inTalkWith(state, actor, focusIds);
|
|
214
|
+
const keeps = actorKeeps(state, actor.id, close);
|
|
215
|
+
const scene = describeScene(state, actor.placeId, null, keeps);
|
|
216
|
+
return fitInput({
|
|
217
|
+
phase: 'attempt',
|
|
218
|
+
clock: clockLabel(state.clock),
|
|
219
|
+
// The present as the count a time is written in: minutes since Day 1, 00:00.
|
|
220
|
+
clockMinutes: state.clock,
|
|
221
|
+
// The opening was written for the moment the world began; the clock shows how long ago that was.
|
|
222
|
+
setting: { world: state.setting, opening: { ...state.opening, ...(state.startClock === undefined ? {} : { asOf: clockLabel(state.startClock) }) }, ...messageTravel(state) },
|
|
223
|
+
actor: { id: actor.id, name: actor.name, role: actor.role, pronouns: actor.pronouns ?? 'they/them', isPlayer: actor.isPlayer, money: actor.money, mood: actor.mood, incapacitated: actor.incapacitated, holding: [...Object.values(state.objects).filter(object => object.holderId === actor.id).map(object => ({ id: object.id, name: object.name, condition: object.condition })),
|
|
224
|
+
...(state.entityPlaceIds[actor.placeId] ?? []).filter(id => state.entities[id].holderId === actor.id).map(id => ({ id, name: state.entities[id].name }))],
|
|
225
|
+
// The player votes with their own vote command; a ruling casts only a character's own vote.
|
|
226
|
+
...(actor.isPlayer ? {} : openVotes(state, actor.id)) },
|
|
227
|
+
narrationVoice: actor.isPlayer ? 'second person ("you"), present tense' : `third person by name (${actor.name}), present tense`,
|
|
228
|
+
attempt,
|
|
229
|
+
// What the actor said here lately, word for word, for an act that writes or repeats it.
|
|
230
|
+
actorSaid: state.conversations.filter(conversation => conversation.placeId === actor.placeId).flatMap(conversation => conversation.lines)
|
|
231
|
+
.filter(line => line.speakerId === actor.id).sort((left, right) => left.seq - right.seq).slice(-3).map(line => ({ at: clockLabel(line.at), text: line.text })),
|
|
232
|
+
...(finishing ? { finishing: { note: FINISHING_NOTE, ...finishing } } : {}),
|
|
233
|
+
scene,
|
|
234
|
+
...moneyAt(state, actor.placeId, actor.id),
|
|
235
|
+
...deadlinesNow(state, true),
|
|
236
|
+
...promisesAt(state, actor.placeId),
|
|
237
|
+
kinds: kindsInScope(state, actor.placeId),
|
|
238
|
+
...writeRule(state),
|
|
239
|
+
requests: relevantRequests(state.requests.filter(request => request.senderId === actor.id
|
|
240
|
+
&& request.knownTo.includes(actor.id) && scene.people.some(person => person.id === request.receiverId)))
|
|
241
|
+
.map(request => ({ id: request.id, toId: request.receiverId,
|
|
242
|
+
topic: request.topic, status: request.status })),
|
|
243
|
+
recentHere: recentEventsAt(state, actor.placeId, 8, null),
|
|
244
|
+
// Serious events already listed here are not listed twice.
|
|
245
|
+
recentSerious: recentImportant(state, 8, recentSeqsAt(state, actor.placeId, 8)),
|
|
246
|
+
world: worldDirectory(state, actor.placeId),
|
|
247
|
+
possiblePerceiverIds: scene.people.map(person => person.id),
|
|
248
|
+
presentCharacterIds: scene.people.filter(person => !state.people[person.id].isPlayer && !person.incapacitated).map(person => person.id),
|
|
249
|
+
...(previousErrors.length === 0 ? {} : { previousErrors: [...previousErrors] }),
|
|
250
|
+
}, REFEREE_INPUT_BUDGET, refereeTrim(state, keeps, close), close, keeps);
|
|
251
|
+
}
|
|
252
|
+
// The open promises of the people where a ruling happens, so what they do keeps or breaks them knowingly.
|
|
253
|
+
function promisesAt(state, placeId) {
|
|
254
|
+
const here = new Set(peopleAt(state, placeId));
|
|
255
|
+
const promises = Object.values(state.people).flatMap(person => person.commitments.filter(commitment => commitment.status === 'open'
|
|
256
|
+
&& (here.has(person.id) || here.has(commitment.toId))).map(commitment => ({ by: person.name, to: personLabel(state, commitment.toId), text: commitment.text,
|
|
257
|
+
...(commitment.dueAt === null ? {} : { due: clockLabel(commitment.dueAt) }) }))).slice(-MIND_SLICE);
|
|
258
|
+
return promises.length === 0 ? {} : { promises };
|
|
259
|
+
}
|
|
260
|
+
// The money at stake where a ruling happens, in the world's unit: the purses of the actor and of people
|
|
261
|
+
// there, and the accounts kept apart from anyone's purse, with who may move each.
|
|
262
|
+
function moneyAt(state, placeId, actorId) {
|
|
263
|
+
if (state.currency === null)
|
|
264
|
+
return {};
|
|
265
|
+
const purses = Object.values(state.people).filter(person => person.money !== null && person.departed === undefined
|
|
266
|
+
&& (person.id === actorId || person.placeId === placeId && person.away === undefined)).map(person => ({ id: person.id, name: person.name, balance: person.money }));
|
|
267
|
+
const accounts = Object.values(state.accounts ?? {}).map(account => ({ id: account.id, name: account.name, balance: account.balance,
|
|
268
|
+
movedBy: account.holderId === null ? 'the world\'s own rules' : personLabel(state, account.holderId) }));
|
|
269
|
+
return purses.length + accounts.length === 0 ? {} : { money: { unit: state.currency.name, balances: [...purses, ...accounts] } };
|
|
270
|
+
}
|
|
271
|
+
// The things an actor holds, those their last act concerned, and the newest things in the hands of the
|
|
272
|
+
// people they deal with here stay in a ruling's view longest, so it can read and hand on what is in play.
|
|
273
|
+
function actorKeeps(state, actorId, close) {
|
|
274
|
+
const last = [...state.events].reverse().find(event => event.actorId === actorId && event.thingIds !== undefined);
|
|
275
|
+
const dealing = (state.entityPlaceIds[state.people[actorId].placeId] ?? []).filter(id => {
|
|
276
|
+
const holderId = state.entities[id].holderId;
|
|
277
|
+
return holderId !== null && holderId !== actorId && close.has(holderId);
|
|
278
|
+
}).slice(-MIND_SLICE);
|
|
279
|
+
return new Set([...Object.values(state.entities).filter(entity => entity.holderId === actorId).map(entity => entity.id), ...(last?.thingIds ?? []), ...dealing]);
|
|
280
|
+
}
|
|
281
|
+
// A due happening is carried out as its world wrote it, and one that comes back is new each time.
|
|
282
|
+
const HAPPENING_NOTE = 'It happens now, as written: show every consequence it names, reaching the people it would reach.';
|
|
283
|
+
const RECURRING_NOTE = 'This time is not like the earlier times.';
|
|
284
|
+
// A later look at a serious act's consequences restates the act; it must not rule the act again.
|
|
285
|
+
const AFTERMATH_NOTE = 'This already happened: rule only what has changed since because of it.';
|
|
286
|
+
// A problem nobody has acted on for a while goes on by itself.
|
|
287
|
+
const STALL_NOTE = 'Nobody has acted on this since about.since: rule how it has gone on by itself since, better, worse or settled; say what is new.';
|
|
288
|
+
const FINISHING_NOTE = 'This act began at begunAt and its time has now passed, but the scene changed meanwhile as changed says. Rule it as done, on the scene as it is now.';
|
|
289
|
+
// In a world where messages take time, everyone knows how they go and how long they take.
|
|
290
|
+
function messageTravel(state) {
|
|
291
|
+
return state.messageTravel ? { messages: `Sent ${state.messageTravel.how}; each arrives about ${state.messageTravel.minutes} minutes after it is sent.` } : {};
|
|
292
|
+
}
|
|
293
|
+
// What a happening concerns, by id, name and kind.
|
|
294
|
+
function concerned(state, id) {
|
|
295
|
+
const entity = state.entities[id];
|
|
296
|
+
if (entity)
|
|
297
|
+
return { id, name: entity.name, kind: state.kinds[entity.kindId]?.name ?? entity.kindId };
|
|
298
|
+
if (state.objects[id])
|
|
299
|
+
return { id, name: state.objects[id].name, kind: 'Object' };
|
|
300
|
+
if (state.people[id])
|
|
301
|
+
return { id, name: state.people[id].name, kind: 'Person' };
|
|
302
|
+
if (state.places[id])
|
|
303
|
+
return { id, name: state.places[id].name, kind: 'Place' };
|
|
304
|
+
const tension = state.tensions.find(candidate => candidate.id === id);
|
|
305
|
+
// A problem says since when it has stood as it is.
|
|
306
|
+
return tension ? { id, name: tension.title, kind: 'Problem', since: clockLabel(tension.changedAt) } : undefined;
|
|
307
|
+
}
|
|
308
|
+
// A happening that comes back remembers its last few times, so each one can be new.
|
|
309
|
+
export function earlierTimes(state, sourceSeq) {
|
|
310
|
+
const earlier = [];
|
|
311
|
+
for (let seq = sourceSeq; seq > 0 && earlier.length < 3;) {
|
|
312
|
+
const before = state.events.find(event => event.seq === seq);
|
|
313
|
+
if (!before || before.kind !== 'world')
|
|
314
|
+
break;
|
|
315
|
+
earlier.push({ at: clockLabel(before.at), what: before.observed ?? before.summary });
|
|
316
|
+
seq = before.causeSeq ?? 0;
|
|
317
|
+
}
|
|
318
|
+
return earlier;
|
|
319
|
+
}
|
|
320
|
+
// A world happening that fell due: an arrival, a spreading fire, a storm, a delivery.
|
|
321
|
+
export function gameMasterWorldInput(state, happening, previousErrors = [], _fitted = true, focusIds = []) {
|
|
322
|
+
const scene = describeScene(state, happening.placeId, null);
|
|
323
|
+
const cause = state.events.find(event => event.seq === happening.sourceSeq);
|
|
324
|
+
const about = happening.targetId ? concerned(state, happening.targetId) : undefined;
|
|
325
|
+
const earlier = happening.everyMinutes === undefined ? [] : earlierTimes(state, happening.sourceSeq);
|
|
326
|
+
return fitInput({
|
|
327
|
+
phase: 'world',
|
|
328
|
+
clock: clockLabel(state.clock),
|
|
329
|
+
clockMinutes: state.clock,
|
|
330
|
+
// The opening was written for the moment the world began; the clock shows how long ago that was.
|
|
331
|
+
setting: { world: state.setting, opening: { ...state.opening, ...(state.startClock === undefined ? {} : { asOf: clockLabel(state.startClock) }) }, ...messageTravel(state) },
|
|
332
|
+
happening: happening.intent,
|
|
333
|
+
// What the world's own data says this happening has made true; the narration agrees with it.
|
|
334
|
+
...(happening.alreadyTrue && happening.alreadyTrue.length > 0 ? { nowTrue: [...happening.alreadyTrue] } : {}),
|
|
335
|
+
happeningNote: happening.id?.startsWith('aftermath-') ? AFTERMATH_NOTE : happening.id?.startsWith('stall-check') ? STALL_NOTE : happening.everyMinutes === undefined ? HAPPENING_NOTE : `${HAPPENING_NOTE} ${RECURRING_NOTE}`,
|
|
336
|
+
// What a happening concerns, such as the fire a danger check is about or the expiry a deadline made.
|
|
337
|
+
...(about === undefined ? {} : { about }),
|
|
338
|
+
...(happening.everyMinutes === undefined ? {} : { recurring: { everyMinutes: happening.everyMinutes, earlier } }),
|
|
339
|
+
cause: cause ? { at: clockLabel(cause.at), summary: cause.summary } : null,
|
|
340
|
+
narrationVoice: 'third person, present tense; nobody here is "you"',
|
|
341
|
+
scene,
|
|
342
|
+
...moneyAt(state, happening.placeId, null),
|
|
343
|
+
...deadlinesNow(state, true),
|
|
344
|
+
...promisesAt(state, happening.placeId),
|
|
345
|
+
kinds: kindsInScope(state, happening.placeId),
|
|
346
|
+
...writeRule(state),
|
|
347
|
+
// The cause is its own field, so it is left out of the recent events here.
|
|
348
|
+
recentHere: recentEventsAt(state, happening.placeId, 8, null, happening.sourceSeq),
|
|
349
|
+
recentSerious: recentImportant(state, 8, recentSeqsAt(state, happening.placeId, 8)),
|
|
350
|
+
world: worldDirectory(state, happening.placeId),
|
|
351
|
+
possiblePerceiverIds: scene.people.map(person => person.id),
|
|
352
|
+
presentCharacterIds: scene.people.filter(person => !state.people[person.id].isPlayer && !person.incapacitated).map(person => person.id),
|
|
353
|
+
...(previousErrors.length === 0 ? {} : { previousErrors: [...previousErrors] }),
|
|
354
|
+
}, REFEREE_INPUT_BUDGET, refereeTrim(state, new Set([...focusIds, ...(happening.targetId ? [happening.targetId] : [])]), new Set([...focusIds, ...(happening.targetId ? [happening.targetId] : [])])), new Set([...focusIds, ...(happening.targetId ? [happening.targetId] : [])]));
|
|
355
|
+
}
|
|
356
|
+
// What weighs on a person right now: the serious things they saw, noticed or were told lately.
|
|
357
|
+
export function pressingMatters(state, person, limit) {
|
|
358
|
+
return pressingOf(state, person, limit)
|
|
359
|
+
.map(memory => ({ when: clockLabel(memory.at), minutesAgo: Math.max(0, state.clock - memory.at), text: memory.text }));
|
|
360
|
+
}
|
|
361
|
+
function pressingOf(state, person, limit) {
|
|
362
|
+
const unresolved = new Set(state.events.filter(event => event.effects.some(effect => {
|
|
363
|
+
if (effect.kind === 'place.condition')
|
|
364
|
+
return effect.add !== null && effect.aspect !== undefined
|
|
365
|
+
&& state.places[effect.placeId]?.conditionAspects[effect.aspect]?.changedSeq === event.seq;
|
|
366
|
+
if (effect.kind === 'person.condition')
|
|
367
|
+
return effect.condition !== null && state.people[effect.personId]?.conditionChangedSeq === event.seq;
|
|
368
|
+
return false;
|
|
369
|
+
})).map(event => event.seq));
|
|
370
|
+
return pressingMemories(state, person, limit, unresolved);
|
|
371
|
+
}
|
|
372
|
+
// The latest pressing memories by time, whatever order a load brought them in.
|
|
373
|
+
function pressingMemories(state, person, limit, unresolved) {
|
|
374
|
+
return person.memories
|
|
375
|
+
.filter(memory => memory.importance >= PRESSING_IMPORTANCE && (memory.at >= state.clock - PRESSING_MINUTES || unresolved.has(memory.eventSeq)))
|
|
376
|
+
.sort((left, right) => left.at - right.at)
|
|
377
|
+
.slice(-limit);
|
|
378
|
+
}
|
|
379
|
+
export function myMovements(state, person, limit) {
|
|
380
|
+
const moves = state.events
|
|
381
|
+
.filter(event => event.effects.some(effect => effect.kind === 'move' && effect.personId === person.id))
|
|
382
|
+
.slice(-limit)
|
|
383
|
+
.map((event) => {
|
|
384
|
+
const move = event.effects.find(effect => effect.kind === 'move' && effect.personId === person.id);
|
|
385
|
+
return `${clockLabel(event.at)}: went to ${move.kind === 'move' ? placeLabel(state, move.placeId) : ''}`;
|
|
386
|
+
});
|
|
387
|
+
return [...moves, `${clockLabel(state.clock)}: now in ${placeLabel(state, person.placeId)}`];
|
|
388
|
+
}
|
|
389
|
+
// A text field keeps its newest items until its size budget is spent, so its size stays flat as a
|
|
390
|
+
// world ages and its lines grow longer. Items arrive oldest first; the newest one always fits.
|
|
391
|
+
const FIELD_BUDGET = { conversation: 2400, myRecentLines: 1800, memories: 2400, recentlySeen: 1000, pressing: 1200, summaries: 1200 };
|
|
392
|
+
// A summary card keeps its opening, most weighty lines within this many characters, so one always fits.
|
|
393
|
+
const SUMMARY_CARD_TEXT = 1000;
|
|
394
|
+
// Fields that tell the same kind of thing share one size, so a person with more pressing news or
|
|
395
|
+
// more heard talk recalls fewer older things instead of getting a longer input as the world ages.
|
|
396
|
+
const HISTORY_BUDGET = 4400;
|
|
397
|
+
const TALK_BUDGET = 2600;
|
|
398
|
+
const OWN_LINES_ROOM = 600;
|
|
399
|
+
// The newest items that fit. The first field of a group keeps at least one item; later fields
|
|
400
|
+
// get only the room the group has left.
|
|
401
|
+
// The first items that fit, in order.
|
|
402
|
+
function firstWithinBudget(items, budget) {
|
|
403
|
+
const kept = [];
|
|
404
|
+
let used = 0;
|
|
405
|
+
for (const item of items) {
|
|
406
|
+
const size = JSON.stringify(item).length + 1;
|
|
407
|
+
if (used + size > budget)
|
|
408
|
+
break;
|
|
409
|
+
kept.push(item);
|
|
410
|
+
used += size;
|
|
411
|
+
}
|
|
412
|
+
return kept;
|
|
413
|
+
}
|
|
414
|
+
function withinBudget(items, budget, keepOne = true) {
|
|
415
|
+
const kept = [];
|
|
416
|
+
let used = 0;
|
|
417
|
+
for (const item of [...items].reverse()) {
|
|
418
|
+
const size = JSON.stringify(item).length + 1;
|
|
419
|
+
if ((kept.length > 0 || !keepOne) && used + size > budget)
|
|
420
|
+
break;
|
|
421
|
+
kept.unshift(item);
|
|
422
|
+
used += size;
|
|
423
|
+
}
|
|
424
|
+
return kept;
|
|
425
|
+
}
|
|
426
|
+
// Each model call sees a window of the world of one size, however much the world holds. When an
|
|
427
|
+
// input runs over, the least needed lists give up items in turn, tier by tier, until it fits.
|
|
428
|
+
const CHARACTER_INPUT_BUDGET = 10_000;
|
|
429
|
+
const REFEREE_INPUT_BUDGET = 12_000;
|
|
430
|
+
function fitInput(input, budget, tiers, close = new Set(), things = new Set()) {
|
|
431
|
+
// Leave room for the bounded lookup result on a follow-up transmission.
|
|
432
|
+
const maximum = decisionInputBudget(input, budget) - 512;
|
|
433
|
+
budget -= 512;
|
|
434
|
+
const fitted = structuredClone(input);
|
|
435
|
+
const priority = selectModelPeople(fitted, close);
|
|
436
|
+
syncModelPeople(fitted);
|
|
437
|
+
let size = JSON.stringify(fitted).length;
|
|
438
|
+
if (size <= budget)
|
|
439
|
+
return fitted;
|
|
440
|
+
const listAt = (path) => {
|
|
441
|
+
let node = fitted;
|
|
442
|
+
for (const key of path)
|
|
443
|
+
node = node !== null && typeof node === 'object' ? node[key] : undefined;
|
|
444
|
+
return Array.isArray(node) ? node : null;
|
|
445
|
+
};
|
|
446
|
+
const kept = (item) => {
|
|
447
|
+
const id = String(item?.id);
|
|
448
|
+
return priority.has(id) || things.has(id);
|
|
449
|
+
};
|
|
450
|
+
for (const tier of tiers) {
|
|
451
|
+
let removed = true;
|
|
452
|
+
while (size > budget && removed) {
|
|
453
|
+
removed = false;
|
|
454
|
+
for (const rule of tier) {
|
|
455
|
+
if (size <= budget)
|
|
456
|
+
break;
|
|
457
|
+
const list = listAt(rule.path);
|
|
458
|
+
if (list === null || list.length <= (rule.keep ?? 0))
|
|
459
|
+
continue;
|
|
460
|
+
let index = rule.drop === 'first' ? 0 : rule.drop === 'last' ? list.length - 1 : rule.drop(list);
|
|
461
|
+
// A rule may find nothing it is allowed to drop.
|
|
462
|
+
if (index < 0)
|
|
463
|
+
continue;
|
|
464
|
+
if (kept(list[index])) {
|
|
465
|
+
index = list.length - 1;
|
|
466
|
+
while (index >= 0 && kept(list[index]))
|
|
467
|
+
index -= 1;
|
|
468
|
+
}
|
|
469
|
+
if (index < 0)
|
|
470
|
+
continue;
|
|
471
|
+
list.splice(index, 1);
|
|
472
|
+
fitted.view = MODEL_VIEW_NOTE;
|
|
473
|
+
syncModelPeople(fitted);
|
|
474
|
+
size = JSON.stringify(fitted).length;
|
|
475
|
+
removed = true;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
compactModelInput(fitted, budget, priority, maximum, things);
|
|
480
|
+
syncModelPeople(fitted);
|
|
481
|
+
return fitted;
|
|
482
|
+
}
|
|
483
|
+
// Things at the place that changed longest ago go first; things with no place stay longer, and what
|
|
484
|
+
// the actor holds or just dealt with stays longest.
|
|
485
|
+
function stalestThing(state, kept = new Set()) {
|
|
486
|
+
const lastChanged = (id) => {
|
|
487
|
+
const entity = state.entities[id];
|
|
488
|
+
if (entity === undefined)
|
|
489
|
+
return 0;
|
|
490
|
+
return Math.max(entity.introducedSeq, ...Object.values(entity.properties).map(slot => slot.changedSeq), ...Object.values(entity.relations).map(slot => slot.changedSeq));
|
|
491
|
+
};
|
|
492
|
+
return (items) => {
|
|
493
|
+
let chosen = 0;
|
|
494
|
+
let chosenKey = [Infinity, Infinity];
|
|
495
|
+
items.forEach((item, index) => {
|
|
496
|
+
const id = item.id;
|
|
497
|
+
const key = [kept.has(id) ? 2 : state.entities[id]?.placeId === null ? 1 : 0, lastChanged(id)];
|
|
498
|
+
if (key[0] < chosenKey[0] || (key[0] === chosenKey[0] && key[1] < chosenKey[1])) {
|
|
499
|
+
chosen = index;
|
|
500
|
+
chosenKey = key;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
return chosen;
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
// Settled asks go before open or refused ones, oldest first within each.
|
|
507
|
+
function settledAskFirst(items) {
|
|
508
|
+
const settled = items.findIndex(item => !['open', 'refused'].includes(item.status));
|
|
509
|
+
return settled >= 0 ? settled : 0;
|
|
510
|
+
}
|
|
511
|
+
// People known but not here go first from a directory, the most recently listed first; someone this
|
|
512
|
+
// person deals with is never dropped, so their name and pronouns stay known.
|
|
513
|
+
function elsewhereFirst(kept) {
|
|
514
|
+
return (items) => {
|
|
515
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
516
|
+
const entry = items[index];
|
|
517
|
+
if (entry.here !== true && !kept.has(entry.id))
|
|
518
|
+
return index;
|
|
519
|
+
}
|
|
520
|
+
return -1;
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
// The people a person deals with: those here, those they talk with, feel about, ask or owe, and whom their plan is about.
|
|
524
|
+
function dealtWith(state, person) {
|
|
525
|
+
return new Set([...peopleAt(state, person.placeId), ...Object.keys(person.relationships), ...person.commitments.map(commitment => commitment.toId),
|
|
526
|
+
...(person.plan?.targetId ? [person.plan.targetId] : []),
|
|
527
|
+
...state.requests.filter(request => request.senderId === person.id || request.receiverId === person.id).flatMap(request => [request.senderId, request.receiverId]),
|
|
528
|
+
...state.conversations.filter(conversation => conversation.placeId === person.placeId && conversation.endedAt === null).flatMap(conversation => conversation.participantIds)]);
|
|
529
|
+
}
|
|
530
|
+
// The people someone is dealing with now: the player, whom the moment is about, who spoke or was spoken
|
|
531
|
+
// to in the last lines of their talk here, their plan's person and those they have an open ask with.
|
|
532
|
+
const TALK_LINES_IN_VIEW = 6;
|
|
533
|
+
function inTalkWith(state, person, focusIds) {
|
|
534
|
+
return new Set([...focusIds.filter((id) => typeof id === 'string'), person.id, state.playerId, ...(person.plan?.targetId ? [person.plan.targetId] : []),
|
|
535
|
+
...state.conversations.filter(conversation => conversation.placeId === person.placeId && conversation.endedAt === null && conversation.participantIds.includes(person.id))
|
|
536
|
+
.flatMap(conversation => conversation.lines.slice(-TALK_LINES_IN_VIEW).flatMap(line => [line.speakerId, ...line.toIds])),
|
|
537
|
+
...state.requests.filter(request => request.status === 'open' && (request.senderId === person.id || request.receiverId === person.id)).flatMap(request => [request.senderId, request.receiverId])]);
|
|
538
|
+
}
|
|
539
|
+
// In a crowded room the people least bound to the moment leave the scene first, the latest listed
|
|
540
|
+
// first; the directory still names everyone here, and at least four stay in full.
|
|
541
|
+
const SCENE_PEOPLE_KEPT = 4;
|
|
542
|
+
function leastInvolved(close, isPerson = () => true) {
|
|
543
|
+
return (items) => {
|
|
544
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
545
|
+
const id = items[index].id;
|
|
546
|
+
if (!close.has(id) && isPerson(id))
|
|
547
|
+
return index;
|
|
548
|
+
}
|
|
549
|
+
return -1;
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
// Weighing the player's words sees an input of one size, however full the room: people elsewhere give
|
|
553
|
+
// way first, then the things changed longest ago and the people here the player deals with least.
|
|
554
|
+
const SPEECH_INPUT_BUDGET = 3_000;
|
|
555
|
+
// The opening gives the words their stakes; held to a bound, it never crowds out the people who hear them.
|
|
556
|
+
const SPEECH_SITUATION = 600;
|
|
557
|
+
const SPEECH_STAKES = 300;
|
|
558
|
+
export function speechSetting(state) {
|
|
559
|
+
return { title: state.opening.title, situation: state.opening.situation.slice(0, SPEECH_SITUATION), stakes: state.opening.stakes.slice(0, SPEECH_STAKES) };
|
|
560
|
+
}
|
|
561
|
+
export function fitSpeechInput(state, input, close, held) {
|
|
562
|
+
return fitInput(input, SPEECH_INPUT_BUDGET, [[{ path: ['elsewhere'], drop: 'last' }],
|
|
563
|
+
[{ path: ['things'], drop: stalestThing(state, held), keep: 4 }, { path: ['listeners'], drop: leastInvolved(close), keep: SCENE_PEOPLE_KEPT }]], close);
|
|
564
|
+
}
|
|
565
|
+
function characterTrim(state, kept = new Set(), close = new Set()) {
|
|
566
|
+
return [
|
|
567
|
+
[{ path: ['landmarks'], drop: 'first', keep: 1 }, { path: ['myEarlierActivities'], drop: 'first', keep: 2 }, { path: ['myRecentLines'], drop: 'first', keep: 1 }, { path: ['myMovements'], drop: 'first', keep: 1 }],
|
|
568
|
+
[{ path: ['scene', 'people'], drop: leastInvolved(close), keep: SCENE_PEOPLE_KEPT },
|
|
569
|
+
{ path: ['scene', 'entities'], drop: stalestThing(state), keep: 2 }, { path: ['requests'], drop: settledAskFirst, keep: 2 }, { path: ['beliefs'], drop: 'last', keep: 1 },
|
|
570
|
+
{ path: ['recentlySeen'], drop: 'first', keep: 2 }, { path: ['directory', 'people'], drop: elsewhereFirst(kept), keep: 0 }],
|
|
571
|
+
[{ path: ['conversation'], drop: 'first', keep: 2 }, { path: ['scene', 'objects'], drop: 'first', keep: 2 }, { path: ['stances'], drop: 'last', keep: 1 }, { path: ['commitments'], drop: 'last', keep: 1 },
|
|
572
|
+
// What this person recalls is cut late, least bound to the moment first, and a little always stays.
|
|
573
|
+
{ path: ['memories'], drop: 'last', keep: 2 }, { path: ['summaries'], drop: 'last', keep: 1 }],
|
|
574
|
+
[{ path: ['pressing'], drop: 'first', keep: 1 }],
|
|
575
|
+
];
|
|
576
|
+
}
|
|
577
|
+
function refereeTrim(state, kept = new Set(), close = new Set()) {
|
|
578
|
+
return [
|
|
579
|
+
[{ path: ['world', 'facts'], drop: 'first' }, { path: ['world', 'objects'], drop: 'first' }],
|
|
580
|
+
// A ruling names anyone present who notices from the ids it is given; the crowd's details give way.
|
|
581
|
+
[{ path: ['scene', 'people'], drop: leastInvolved(close), keep: SCENE_PEOPLE_KEPT }, { path: ['world', 'people'], drop: leastInvolved(close), keep: SCENE_PEOPLE_KEPT },
|
|
582
|
+
{ path: ['money', 'balances'], drop: leastInvolved(close, id => state.people[id] !== undefined), keep: SCENE_PEOPLE_KEPT },
|
|
583
|
+
{ path: ['scene', 'entities'], drop: stalestThing(state, kept), keep: 2 }, { path: ['recentHere'], drop: 'first', keep: 2 }, { path: ['world', 'tensions'], drop: 'first', keep: 1 }],
|
|
584
|
+
[{ path: ['requests'], drop: settledAskFirst, keep: 2 }, { path: ['scene', 'objects'], drop: 'first', keep: 2 }, { path: ['world', 'elsewhere'], drop: 'first' }, { path: ['promises'], drop: 'first', keep: 2 }],
|
|
585
|
+
// The record of what really happened goes last, and its newest three stay.
|
|
586
|
+
[{ path: ['recentSerious'], drop: 'first', keep: 3 }],
|
|
587
|
+
];
|
|
588
|
+
}
|
|
589
|
+
function spent(items) {
|
|
590
|
+
return items.reduce((total, item) => total + JSON.stringify(item).length + 1, 0);
|
|
591
|
+
}
|
|
592
|
+
// Each memory, line and plan appears once: pressing memories first, then the latest others, then
|
|
593
|
+
// recalled older ones; the conversation, then the person's own lines elsewhere.
|
|
594
|
+
export function characterMind(state, person, focusIds, shownElsewhere = {}) {
|
|
595
|
+
const scene = describeScene(state, person.placeId, person.id);
|
|
596
|
+
const relationships = Object.entries(person.relationships)
|
|
597
|
+
.filter(([id]) => state.people[id] && (scene.people.some(other => other.id === id) || person.memories.some(memory => memory.participantIds.includes(id))))
|
|
598
|
+
// Why their feelings last changed, so a confession or a betrayal is not forgotten.
|
|
599
|
+
.map(([id, relationship]) => ({ id, name: personLabel(state, id), trust: relationship.trust, warmth: relationship.warmth, ...(relationship.note ? { note: relationship.note } : {}) }));
|
|
600
|
+
const heard = heardLines(state, person.placeId, person.id, 12);
|
|
601
|
+
const trigger = shownElsewhere.line;
|
|
602
|
+
const triggerLine = trigger === undefined ? undefined
|
|
603
|
+
: [...heard].reverse().find(line => line.speakerId === trigger.speakerId && line.text === trigger.text);
|
|
604
|
+
// The conversation leaves room in the talk budget for at least the person's latest line elsewhere.
|
|
605
|
+
const conversation = withinBudget(heard.filter(line => line !== triggerLine).map(line => lineCard(state, line)), Math.min(FIELD_BUDGET.conversation, TALK_BUDGET - OWN_LINES_ROOM));
|
|
606
|
+
const heardSeqs = new Set(heard.map(line => line.seq));
|
|
607
|
+
// A line already in the conversation, or the one being answered, is not listed again as news.
|
|
608
|
+
const inTalk = new Set([...heard.slice(-conversation.length - (triggerLine ? 1 : 0)).map(line => line.seq), ...(triggerLine ? [triggerLine.seq] : [])]);
|
|
609
|
+
const pressing = pressingOf(state, person, 6).filter(memory => !inTalk.has(memory.eventSeq));
|
|
610
|
+
const pressingIds = new Set(pressing.map(memory => memory.id));
|
|
611
|
+
const latest = person.memories.filter(memory => !pressingIds.has(memory.id) && !inTalk.has(memory.eventSeq)).sort((left, right) => left.at - right.at).slice(-6);
|
|
612
|
+
const shownIds = new Set([...pressingIds, ...latest.map(memory => memory.id)]);
|
|
613
|
+
// History fills in order of urgency: pressing news, the last things seen, long-term summaries,
|
|
614
|
+
// then recalled older memories in whatever room is left.
|
|
615
|
+
const pressingCards = withinBudget(pressing.map(memory => ({ id: memory.id, when: clockLabel(memory.at), minutesAgo: Math.max(0, state.clock - memory.at), text: memory.text })), FIELD_BUDGET.pressing);
|
|
616
|
+
// The last things this person saw or was told, whatever the cues: nobody forgets the last hour.
|
|
617
|
+
const recentCards = withinBudget(latest.map(memory => ({ id: memory.id, when: clockLabel(memory.at), text: memory.text, evidence: memory.evidence?.kind ?? 'legacy' })), Math.min(FIELD_BUDGET.recentlySeen, HISTORY_BUDGET - spent(pressingCards)));
|
|
618
|
+
// Summaries come best first, so the budget keeps them from the front.
|
|
619
|
+
const summaryCards = withinBudget((person.summaries ?? []).filter(summary => summary.personId === person.id)
|
|
620
|
+
.map(summary => ({ summary, relevance: summary.participantIds.some(id => focusIds.includes(id))
|
|
621
|
+
|| summary.placeIds.some(id => focusIds.includes(id)) ? 1 : 0 }))
|
|
622
|
+
.sort((left, right) => right.relevance - left.relevance || right.summary.importance - left.summary.importance
|
|
623
|
+
|| right.summary.at - left.summary.at).slice(0, 3)
|
|
624
|
+
.map(({ summary }) => ({ id: summary.id, when: clockLabel(summary.at), text: summary.text.slice(0, SUMMARY_CARD_TEXT),
|
|
625
|
+
importance: summary.importance })).reverse(), Math.min(FIELD_BUDGET.summaries, HISTORY_BUDGET - spent(pressingCards) - spent(recentCards)), false).reverse();
|
|
626
|
+
// Recall reaches past the talk under way to older things.
|
|
627
|
+
const talkMemoryIds = person.memories.filter(memory => heardSeqs.has(memory.eventSeq)).map(memory => memory.id);
|
|
628
|
+
// Recalled memories come best first; a cut takes the least bound to the moment, never the oldest first.
|
|
629
|
+
const memoryCards = firstWithinBudget(recallMemories(state, person, focusIds, 8, new Set([...shownIds, ...talkMemoryIds]), true).map(memory => ({ id: memory.id, when: clockLabel(memory.at), text: memory.text, importance: memory.importance,
|
|
630
|
+
evidence: memory.evidence?.kind ?? 'legacy' })), Math.min(FIELD_BUDGET.memories, HISTORY_BUDGET - spent(pressingCards) - spent(recentCards) - spent(summaryCards)));
|
|
631
|
+
// The gravest things this person ever lived through stay with them, however long ago.
|
|
632
|
+
const listed = new Set([...shownIds, ...memoryCards.map(card => card.id)]);
|
|
633
|
+
const landmarkCards = person.memories.filter(memory => memory.importance >= LANDMARK_IMPORTANCE && !listed.has(memory.id))
|
|
634
|
+
.sort((left, right) => right.importance - left.importance || right.at - left.at).slice(0, LANDMARKS).sort((left, right) => left.at - right.at)
|
|
635
|
+
.map(memory => ({ id: memory.id, when: clockLabel(memory.at), text: memory.text, importance: memory.importance, evidence: memory.evidence?.kind ?? 'legacy' }));
|
|
636
|
+
return {
|
|
637
|
+
setting: state.setting,
|
|
638
|
+
...messageTravel(state),
|
|
639
|
+
identity: { id: person.id, name: person.name, role: person.role, pronouns: person.pronouns ?? 'they/them',
|
|
640
|
+
character: person.character ?? { voice: person.persona, values: [], redLines: [], privateConcerns: [] },
|
|
641
|
+
mood: person.mood, condition: person.condition ?? null, held: person.held ? person.held.reason : null, money: person.money,
|
|
642
|
+
blocked: blockedActionKinds(state, person.id) },
|
|
643
|
+
goals: person.goals.filter(goal => goal.status === 'active').slice(-MIND_SLICE).map(goal => ({ id: goal.id, text: goal.text })),
|
|
644
|
+
relationships,
|
|
645
|
+
pressing: pressingCards,
|
|
646
|
+
memories: memoryCards,
|
|
647
|
+
summaries: summaryCards,
|
|
648
|
+
landmarks: landmarkCards,
|
|
649
|
+
recentlySeen: recentCards,
|
|
650
|
+
// Where this person has actually been, so they never claim to have been somewhere else.
|
|
651
|
+
myMovements: myMovements(state, person, 6),
|
|
652
|
+
// What they took up before their present activity and when, so hours of the same waiting are
|
|
653
|
+
// plain to them; the present one is the plan above.
|
|
654
|
+
myEarlierActivities: (person.planHistory ?? []).slice(0, person.plan ? -1 : undefined).map(item => ({ since: clockLabel(item.at), what: item.intent })),
|
|
655
|
+
beliefs: recallBeliefs(person, focusIds, 6).map(belief => ({ id: belief.id, subject: belief.subject, claim: belief.claim, source: belief.source.kind === 'told' ? `told by ${personLabel(state, belief.source.personId ?? '')}` : belief.source.kind })),
|
|
656
|
+
// What others have promised this person and not yet kept: what they are owed.
|
|
657
|
+
promisedToMe: Object.values(state.people).flatMap(other => other.id === person.id ? [] : other.commitments
|
|
658
|
+
.filter(commitment => commitment.toId === person.id && commitment.status === 'open' && (commitment.knownTo?.includes(person.id) ?? true))
|
|
659
|
+
.map(commitment => ({ other, commitment })))
|
|
660
|
+
.sort((left, right) => left.commitment.madeAt - right.commitment.madeAt).slice(-MIND_SLICE)
|
|
661
|
+
.map(({ other, commitment }) => ({ id: commitment.id, by: other.name, text: commitment.text, due: commitment.dueAt === null ? null : clockLabel(commitment.dueAt) })),
|
|
662
|
+
commitments: person.commitments.filter(commitment => commitment.status === 'open')
|
|
663
|
+
.sort((left, right) => (left.dueAt ?? Infinity) - (right.dueAt ?? Infinity) || left.madeAt - right.madeAt).slice(0, MIND_SLICE)
|
|
664
|
+
.map(commitment => ({ id: commitment.id, to: personLabel(state, commitment.toId), text: commitment.text, due: commitment.dueAt === null ? null : clockLabel(commitment.dueAt),
|
|
665
|
+
...(commitment.dueAt === null ? {} : commitment.dueAt >= state.clock ? { minutesLeft: commitment.dueAt - state.clock } : { minutesPast: state.clock - commitment.dueAt }) })),
|
|
666
|
+
...deadlinesNow(state),
|
|
667
|
+
...ownPapers(state, person.id),
|
|
668
|
+
...papersInMind(state, person, focusIds),
|
|
669
|
+
...awakeSince(state, person),
|
|
670
|
+
requests: relevantRequests(state.requests.filter(request => request.knownTo.includes(person.id)))
|
|
671
|
+
.map(request => ({ id: request.id, fromId: request.senderId, toId: request.receiverId,
|
|
672
|
+
topic: request.topic, status: request.status, ...(request.answer && request.senderId === person.id ? { answer: request.answer.text } : {}),
|
|
673
|
+
// How long one's own open request has waited, so hours of waiting are plain.
|
|
674
|
+
...(request.senderId === person.id && request.status === 'open' && request.openedAt !== undefined ? { waitingMinutes: state.clock - request.openedAt } : {}) })),
|
|
675
|
+
stances: state.stances.filter(stance => stance.personId === person.id)
|
|
676
|
+
.sort((left, right) => Number(scene.people.some(other => other.id === right.actorId)) - Number(scene.people.some(other => other.id === left.actorId))
|
|
677
|
+
|| right.changedSeq - left.changedSeq).slice(0, MIND_SLICE)
|
|
678
|
+
.map(stance => ({ actorId: stance.actorId, status: stance.status, sourceSeq: stance.sourceSeq })),
|
|
679
|
+
plan: person.plan ? { intent: person.plan.intent, due: clockLabel(person.plan.dueAt), placeId: person.plan.placeId, targetId: person.plan.targetId } : null,
|
|
680
|
+
scene,
|
|
681
|
+
conversation,
|
|
682
|
+
myRecentLines: withinBudget(recentLinesBy(state, person.id, 12).filter(line => !heardSeqs.has(line.seq)).map(line => line.text), Math.min(FIELD_BUDGET.myRecentLines, TALK_BUDGET - spent(conversation))),
|
|
683
|
+
clock: clockLabel(state.clock),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function knownPeople(state, person) {
|
|
687
|
+
const ids = new Set(Object.keys(person.relationships));
|
|
688
|
+
// Only those really here are known by being here: not someone away, nor someone who left the story.
|
|
689
|
+
for (const other of Object.values(state.people))
|
|
690
|
+
if (other.placeId === person.placeId && other.away === undefined && other.departed === undefined)
|
|
691
|
+
ids.add(other.id);
|
|
692
|
+
for (const memory of person.memories)
|
|
693
|
+
for (const id of memory.participantIds)
|
|
694
|
+
ids.add(id);
|
|
695
|
+
for (const belief of person.beliefs)
|
|
696
|
+
if (belief.source.personId !== null)
|
|
697
|
+
ids.add(belief.source.personId);
|
|
698
|
+
for (const commitment of person.commitments)
|
|
699
|
+
ids.add(commitment.toId);
|
|
700
|
+
if (person.plan?.targetId)
|
|
701
|
+
ids.add(person.plan.targetId);
|
|
702
|
+
for (const conversation of state.conversations) {
|
|
703
|
+
if (!conversation.participantIds.includes(person.id))
|
|
704
|
+
continue;
|
|
705
|
+
for (const id of conversation.participantIds)
|
|
706
|
+
ids.add(id);
|
|
707
|
+
}
|
|
708
|
+
ids.delete(person.id);
|
|
709
|
+
return ids;
|
|
710
|
+
}
|
|
711
|
+
// Resolve model-supplied references, never words in an action. Unknown and ambiguous names
|
|
712
|
+
// have the same result, so asking cannot expose an identity outside this role's knowledge.
|
|
713
|
+
export function modelContextPeople(state, actorId, phase, input, requested) {
|
|
714
|
+
const actor = actorId === null ? undefined : state.people[actorId];
|
|
715
|
+
const referee = phase === 'attempt' || phase === 'world';
|
|
716
|
+
const placeId = input.scene?.id ?? actor?.placeId;
|
|
717
|
+
const allowed = referee && placeId ? new Set(worldDirectory(state, placeId).people.map(person => person.id))
|
|
718
|
+
: actor ? knownPeople(state, actor) : new Set();
|
|
719
|
+
if (actor)
|
|
720
|
+
allowed.add(actor.id);
|
|
721
|
+
const focusIds = [], unavailable = [];
|
|
722
|
+
for (const reference of requested) {
|
|
723
|
+
const matches = Object.values(state.people).filter(person => allowed.has(person.id) && (person.id === reference || person.name === reference));
|
|
724
|
+
if (matches.length === 1)
|
|
725
|
+
focusIds.push(matches[0].id);
|
|
726
|
+
else
|
|
727
|
+
unavailable.push(reference);
|
|
728
|
+
}
|
|
729
|
+
return { focusIds: [...new Set(focusIds)], unavailable };
|
|
730
|
+
}
|
|
731
|
+
function knownPlaces(state, person) {
|
|
732
|
+
const ids = new Set([person.placeId, ...state.places[person.placeId].connections]);
|
|
733
|
+
for (const memory of person.memories)
|
|
734
|
+
if (memory.placeId !== null)
|
|
735
|
+
ids.add(memory.placeId);
|
|
736
|
+
if (person.plan?.placeId)
|
|
737
|
+
ids.add(person.plan.placeId);
|
|
738
|
+
return ids;
|
|
739
|
+
}
|
|
740
|
+
function spokenTo(state, trigger) {
|
|
741
|
+
for (const event of [...state.events].reverse()) {
|
|
742
|
+
const said = event.effects.find(effect => effect.kind === 'say' && effect.personId === trigger.actorId && effect.text === trigger.text);
|
|
743
|
+
if (said?.kind === 'say')
|
|
744
|
+
return said.toIds.map(id => personLabel(state, id));
|
|
745
|
+
}
|
|
746
|
+
return [];
|
|
747
|
+
}
|
|
748
|
+
export function characterReactionInput(state, person, trigger, _fitted = true, previousErrors = [], focusIds = []) {
|
|
749
|
+
const actorName = trigger.actorId ? personLabel(state, trigger.actorId) : 'The world';
|
|
750
|
+
const noticed = trigger.kind === 'nearby';
|
|
751
|
+
const peopleInScope = knownPeople(state, person);
|
|
752
|
+
// Someone away for a while is not here, wherever they left from.
|
|
753
|
+
const present = (other) => other.placeId === person.placeId && other.away === undefined && other.departed === undefined;
|
|
754
|
+
return fitInput({
|
|
755
|
+
phase: 'reaction',
|
|
756
|
+
...(previousErrors.length === 0 ? {} : { previousErrors: [...previousErrors] }),
|
|
757
|
+
...characterMind(state, person, [...(trigger.actorId === null ? [person.placeId] : [trigger.actorId, person.placeId]), ...(trigger.thingIds ?? [])], trigger.kind === 'speech' ? { line: { speakerId: trigger.actorId, text: trigger.text } } : {}),
|
|
758
|
+
happened: noticed
|
|
759
|
+
? { kind: 'noticed-from-nearby', by: null, byId: null, text: trigger.text, narration: null, from: trigger.noticedFrom ?? null }
|
|
760
|
+
: { kind: trigger.kind, by: actorName, byId: trigger.actorId, text: trigger.text, narration: trigger.narration, pressure: trigger.pressure ?? null,
|
|
761
|
+
// Whom the line was said to, since the line itself is not repeated in the conversation.
|
|
762
|
+
...(trigger.kind === 'speech' ? { to: spokenTo(state, trigger) } : {}) },
|
|
763
|
+
// A vote is offered only in answer to a moment about it, so words about something else cast none.
|
|
764
|
+
...openVotes(state, person.id, new Set(trigger.thingIds ?? [])),
|
|
765
|
+
playerId: state.playerId,
|
|
766
|
+
playerName: peopleInScope.has(state.playerId) ? personLabel(state, state.playerId) : null,
|
|
767
|
+
playerPronouns: peopleInScope.has(state.playerId) ? state.people[state.playerId]?.pronouns ?? 'they/them' : null,
|
|
768
|
+
playerHere: state.people[state.playerId]?.placeId === person.placeId,
|
|
769
|
+
exits: state.places[person.placeId].connections.map(id => ({ id, name: state.places[id]?.name ?? id })),
|
|
770
|
+
directory: { people: Object.values(state.people).filter(other => peopleInScope.has(other.id)).map(other => ({ id: other.id, name: other.name,
|
|
771
|
+
pronouns: other.pronouns ?? 'they/them', role: present(other) ? other.role : null, here: present(other), ...silentSince(state, person.id, other.id) })) },
|
|
772
|
+
}, CHARACTER_INPUT_BUDGET, characterTrim(state, dealtWith(state, person), inTalkWith(state, person, [...focusIds, trigger.actorId])), inTalkWith(state, person, [...focusIds, trigger.actorId]));
|
|
773
|
+
}
|
|
774
|
+
// A deadline whose problem is already settled will not happen; it is no longer counted down to.
|
|
775
|
+
export function avertedAhead(state, item) {
|
|
776
|
+
const settles = item.beat?.unless?.settled;
|
|
777
|
+
return settles !== undefined && state.tensions.some(tension => tension.id === settles && tension.status === 'settled');
|
|
778
|
+
}
|
|
779
|
+
// The world's public deadlines, counted from now: minutes left for those ahead, and minutes past for
|
|
780
|
+
// those that went by in the last day, so no one speaks of a passed deadline as still to come.
|
|
781
|
+
function deadlinesNow(state, withOutcome = false) {
|
|
782
|
+
const ahead = state.schedule.filter(item => item.kind === 'world' && item.beat?.deadline && !avertedAhead(state, item)).sort((left, right) => left.at - right.at).slice(0, 4)
|
|
783
|
+
.map(item => ({ what: item.intent, due: clockLabel(item.at), minutesLeft: Math.max(0, item.at - state.clock) }));
|
|
784
|
+
const past = (state.passedDeadlines ?? []).filter(entry => state.clock - entry.at <= 1440).slice(-3)
|
|
785
|
+
.map(entry => ({ what: entry.what, due: clockLabel(entry.at), minutesPast: state.clock - entry.at, ...(entry.averted ? { averted: true } : {}),
|
|
786
|
+
...(withOutcome ? { outcome: entry.outcome } : {}) }));
|
|
787
|
+
return ahead.length + past.length === 0 ? {} : { deadlines: [...past, ...ahead] };
|
|
788
|
+
}
|
|
789
|
+
// Since when someone up a long day has been awake: since they last came back from rest, or since the story began.
|
|
790
|
+
export function awakeSince(state, person) {
|
|
791
|
+
const since = person.restedAt ?? state.startClock ?? 0;
|
|
792
|
+
return state.clock - since >= LONG_DAY_MINUTES ? { awakeSince: clockLabel(since) } : {};
|
|
793
|
+
}
|
|
794
|
+
// The words of the papers this person wrote, the newest two, wherever the papers now lie.
|
|
795
|
+
export function ownPapers(state, personId) {
|
|
796
|
+
const papers = (state.authoredIds?.[personId] ?? []).slice(-2).map(id => state.entities[id]).flatMap(entity => {
|
|
797
|
+
const slot = entity?.properties.body;
|
|
798
|
+
const perceived = slot?.knownTo.includes(personId) ? { name: entity.name, words: slot.value } : entity?.paperViews?.[personId];
|
|
799
|
+
return entity && typeof perceived?.words === 'string'
|
|
800
|
+
? [{ id: entity.id, name: perceived.name, words: perceived.words.slice(0, OWN_PAPER_WORDS) }] : [];
|
|
801
|
+
});
|
|
802
|
+
return papers.length === 0 ? {} : { myPapers: papers };
|
|
803
|
+
}
|
|
804
|
+
const OWN_PAPER_WORDS = 800;
|
|
805
|
+
// The words of the papers a moment concerns that this person knows, wherever they lie, so no one makes
|
|
806
|
+
// up what a paper they know says. Those here are in the scene, and those they wrote are their own.
|
|
807
|
+
export function papersInMind(state, person, focusIds) {
|
|
808
|
+
const own = new Set(state.authoredIds?.[person.id] ?? []);
|
|
809
|
+
const papers = [...new Set(focusIds)].flatMap(id => {
|
|
810
|
+
const entity = state.entities[id];
|
|
811
|
+
const words = entity?.properties.body;
|
|
812
|
+
return entity && entity.placeId !== person.placeId && !own.has(id) && entity.knownTo.includes(person.id) && typeof words?.value === 'string'
|
|
813
|
+
&& words.knownTo.includes(person.id) && !sealedPaper(state.engine, entity) ? [{ id, name: entity.name, words: words.value.slice(0, OWN_PAPER_WORDS) }] : [];
|
|
814
|
+
}).slice(0, 2);
|
|
815
|
+
return papers.length === 0 ? {} : { papersInMind: papers };
|
|
816
|
+
}
|
|
817
|
+
// The votes this person may cast now: open decisions they are a member of and have not voted on,
|
|
818
|
+
// among those a moment concerns when it names any.
|
|
819
|
+
export function openVotes(state, personId, concerned) {
|
|
820
|
+
const votes = (state.openDecisionIds ?? []).filter(id => concerned === undefined || concerned.has(id)).map(id => state.entities[id])
|
|
821
|
+
.filter(entity => entity.members?.includes(personId) && entity.ballots?.[personId] === undefined && votingShut(state, entity, state.clock) === null).slice(0, 3)
|
|
822
|
+
.map(entity => {
|
|
823
|
+
const closesAt = entity.properties['closes-at']?.value;
|
|
824
|
+
const needed = entity.properties.needed?.value;
|
|
825
|
+
return { id: entity.id, question: String(entity.properties.question?.value ?? entity.name).slice(0, 300), needed: typeof needed === 'number' ? needed : null,
|
|
826
|
+
closes: typeof closesAt === 'number' ? clockLabel(closesAt) : null, count: entity.condition };
|
|
827
|
+
});
|
|
828
|
+
return votes.length === 0 ? {} : { votes };
|
|
829
|
+
}
|
|
830
|
+
// Someone who has left this person's last lines unanswered, since the first of them.
|
|
831
|
+
function silentSince(state, personId, otherId) {
|
|
832
|
+
const since = unansweredSince(state, personId, otherId);
|
|
833
|
+
return since === null ? {} : { noAnswerSince: clockLabel(since) };
|
|
834
|
+
}
|
|
835
|
+
// The due plan is data: what the person meant to do, and how late it now is.
|
|
836
|
+
export function characterPlanInput(state, person, plan, voteTurn = false, focusIds = []) {
|
|
837
|
+
const duePlan = typeof plan === 'string' ? { intent: plan } : plan;
|
|
838
|
+
// A decision is cued by where the person is, whom they are with, and whom their plan is about.
|
|
839
|
+
const mind = characterMind(state, person, [person.placeId, ...peopleAt(state, person.placeId).filter(id => id !== person.id),
|
|
840
|
+
...(person.plan?.targetId ? [person.plan.targetId] : [])]);
|
|
841
|
+
// The due plan is the person's plan; it is listed once, with its place and person.
|
|
842
|
+
const current = mind.plan;
|
|
843
|
+
const due = duePlan !== null && current?.intent === duePlan.intent ? { ...duePlan, placeId: current.placeId, targetId: current.targetId } : duePlan;
|
|
844
|
+
if (due !== duePlan)
|
|
845
|
+
mind.plan = null;
|
|
846
|
+
const peopleInScope = knownPeople(state, person);
|
|
847
|
+
const here = (other) => other.placeId === person.placeId && other.away === undefined;
|
|
848
|
+
const placesInScope = knownPlaces(state, person);
|
|
849
|
+
return fitInput({
|
|
850
|
+
phase: 'plan',
|
|
851
|
+
...mind,
|
|
852
|
+
duePlan: due,
|
|
853
|
+
// A member's turn to vote, when voting opens, is the decision in which they cast it.
|
|
854
|
+
...(voteTurn ? openVotes(state, person.id) : {}),
|
|
855
|
+
directory: {
|
|
856
|
+
places: Object.values(state.places).filter(place => placesInScope.has(place.id)).map(place => ({ id: place.id, name: place.name })),
|
|
857
|
+
people: Object.values(state.people).filter(other => peopleInScope.has(other.id)).map(other => ({ id: other.id, name: other.name,
|
|
858
|
+
pronouns: other.pronouns ?? 'they/them', role: here(other) ? other.role : null, lastKnownPlaceId: here(other) ? other.placeId : null, ...silentSince(state, person.id, other.id) })),
|
|
859
|
+
},
|
|
860
|
+
playerId: state.playerId,
|
|
861
|
+
}, CHARACTER_INPUT_BUDGET, characterTrim(state, dealtWith(state, person), inTalkWith(state, person, [...focusIds, due?.targetId])), inTalkWith(state, person, [...focusIds, due?.targetId]));
|
|
862
|
+
}
|
|
863
|
+
export function characterMessageReplyInput(state, person, message, focusIds = []) {
|
|
864
|
+
const mind = characterMind(state, person, [message.fromId, person.placeId]);
|
|
865
|
+
const placesInScope = knownPlaces(state, person);
|
|
866
|
+
return fitInput({
|
|
867
|
+
phase: 'message-reply',
|
|
868
|
+
...mind,
|
|
869
|
+
message: { fromId: message.fromId, from: personLabel(state, message.fromId), text: message.text },
|
|
870
|
+
directory: { places: Object.values(state.places).filter(place => placesInScope.has(place.id)).map(place => ({ id: place.id, name: place.name })),
|
|
871
|
+
...(focusIds.length === 0 ? {} : { people: focusIds.filter(id => knownPeople(state, person).has(id)).map(id => ({ id, name: state.people[id].name })) }) },
|
|
872
|
+
}, CHARACTER_INPUT_BUDGET, characterTrim(state, dealtWith(state, person), inTalkWith(state, person, [...focusIds, message.fromId])), inTalkWith(state, person, [...focusIds, message.fromId]));
|
|
873
|
+
}
|
|
874
|
+
export function epilogueInput(state) {
|
|
875
|
+
const player = state.people[state.playerId];
|
|
876
|
+
const playerChoices = state.events.filter(event => event.actorId === state.playerId && (event.kind === 'speech' || event.kind === 'message' || event.kind === 'attempt')).slice(-24);
|
|
877
|
+
const choiceSeqs = new Set(playerChoices.map(event => event.seq));
|
|
878
|
+
const otherEvents = state.events.filter(event => !choiceSeqs.has(event.seq) && visibleTo(event, player.id)
|
|
879
|
+
&& (event.importance >= 6 || event.kind === 'attempt' || event.kind === 'world')).slice(-(40 - playerChoices.length));
|
|
880
|
+
const keyEvents = [...playerChoices, ...otherEvents].sort((left, right) => left.seq - right.seq);
|
|
881
|
+
// From engine 40 the ending reads the chronicle of the whole story; the weightiest fifty, in order.
|
|
882
|
+
const chronicled = state.engine >= CHRONICLE_ENGINE
|
|
883
|
+
? [...state.chronicle].sort((left, right) => right.importance - left.importance || right.seq - left.seq).slice(0, 50).sort((left, right) => left.seq - right.seq)
|
|
884
|
+
.map(entry => ({ kind: entry.kind, at: clockLabel(entry.at), place: placeLabel(state, entry.placeId), summary: entry.summary, outcome: entry.outcome, witnesses: [] }))
|
|
885
|
+
: null;
|
|
886
|
+
return fitInput({
|
|
887
|
+
phase: 'epilogue',
|
|
888
|
+
player: { name: player.name, role: player.role, pronouns: player.pronouns ?? 'they/them', place: placeLabel(state, player.placeId), money: player.money, incapacitated: player.incapacitated, forcedEnding: state.forcedEnding },
|
|
889
|
+
opening: state.opening,
|
|
890
|
+
tensions: state.tensions.flatMap(tension => {
|
|
891
|
+
if (!tension.playerView)
|
|
892
|
+
return [];
|
|
893
|
+
const { since: _since, latestAt: _latestAt, ...view } = tension.playerView;
|
|
894
|
+
return [view];
|
|
895
|
+
}),
|
|
896
|
+
...knownVotes(state, player.id),
|
|
897
|
+
relationships: [],
|
|
898
|
+
whereabouts: Object.values(state.people).filter(person => person.id === player.id || person.placeId === player.placeId || state.sightings[person.id]).map(person => ({ name: person.name,
|
|
899
|
+
place: person.id === player.id || person.placeId === player.placeId ? placeLabel(state, person.placeId) : placeLabel(state, state.sightings[person.id].placeId),
|
|
900
|
+
mood: person.id === player.id || person.placeId === player.placeId ? person.mood : null,
|
|
901
|
+
condition: person.id === player.id || person.placeId === player.placeId ? person.condition ?? null : null,
|
|
902
|
+
incapacitated: person.id === player.id || person.placeId === player.placeId ? person.incapacitated : null })),
|
|
903
|
+
places: [{ name: state.places[player.placeId].name, conditions: [...state.places[player.placeId].conditions] }],
|
|
904
|
+
objects: Object.values(state.objects).filter(object => object.holderId === player.id || object.placeId === player.placeId
|
|
905
|
+
|| (object.holderId !== null && knowsCustody(state, object, player.id))).map(object => ({ name: object.name,
|
|
906
|
+
condition: object.holderId === player.id || object.conditionKnownTo.includes(player.id) ? object.condition : 'unexamined',
|
|
907
|
+
where: object.holderId ? `with ${personLabel(state, object.holderId)}` : placeLabel(state, object.placeId) })),
|
|
908
|
+
commitments: Object.values(state.people).flatMap(person => person.commitments
|
|
909
|
+
.filter(commitment => person.id === player.id || commitment.toId === player.id).map(commitment => ({ person, commitment })))
|
|
910
|
+
.sort((left, right) => left.commitment.madeAt - right.commitment.madeAt).slice(-12)
|
|
911
|
+
.map(({ person, commitment }) => ({ by: person.name, to: personLabel(state, commitment.toId), text: commitment.text, status: commitment.status })),
|
|
912
|
+
keyEvents: chronicled ?? keyEvents.flatMap(event => {
|
|
913
|
+
const summary = observedSummary(event, player.id);
|
|
914
|
+
return summary === null ? [] : [{ kind: event.kind, at: clockLabel(event.at), place: event.nearbyIds.includes(player.id) && !witnessed(event, player.id) ? null : placeLabel(state, event.placeId),
|
|
915
|
+
summary, outcome: event.observed === undefined ? event.outcome : null, witnesses: [] }];
|
|
916
|
+
}),
|
|
917
|
+
clock: clockLabel(state.clock),
|
|
918
|
+
}, REFEREE_INPUT_BUDGET, [[{ path: ['whereabouts'], drop: 'last', keep: 1 }, { path: ['objects'], drop: 'first' }, { path: ['keyEvents'], drop: 'first', keep: 8 }, { path: ['tensions'], drop: openFirst, keep: 1 }]]);
|
|
919
|
+
}
|
|
920
|
+
// Problems still open give way before settled ones, whose ends the ending tells; the oldest first within each.
|
|
921
|
+
function openFirst(items) {
|
|
922
|
+
const open = items.findIndex(item => item.status === 'open');
|
|
923
|
+
return open >= 0 ? open : 0;
|
|
924
|
+
}
|
|
925
|
+
// The votes the player knows of: what was asked, what came of it, and the count. A counted vote's roll is
|
|
926
|
+
// its public record; before the count the player knows only their own vote.
|
|
927
|
+
function knownVotes(state, playerId) {
|
|
928
|
+
const votes = Object.values(state.entities).filter(entity => entity.members !== undefined && entity.knownTo.includes(playerId)).slice(-MIND_SLICE)
|
|
929
|
+
.map(entity => ({ name: entity.name, question: String(entity.properties.question?.value ?? entity.name).slice(0, 300), result: entity.result ?? null, count: entity.condition,
|
|
930
|
+
ballots: entity.members.filter(id => entity.ballots?.[id] !== undefined && (entity.result !== undefined || id === playerId))
|
|
931
|
+
.map(id => ({ name: personLabel(state, id), choice: entity.ballots[id] })) }));
|
|
932
|
+
return votes.length === 0 ? {} : { votes };
|
|
933
|
+
}
|
|
934
|
+
export const WORLD_PRINCIPLES = `People know only what they perceive, read or are told. They have their own aims, duties, feelings, and judgment. The setting decides what is physically and socially possible. An act can fail yet still cause a real result. A lasting result needs a typed change in state. Speech is a statement until evidence supports its claim. Describe only what a person in the scene can sense. Keep private thoughts and unseen causes in the objective record. Use ids for known people, things, places, kinds, problems. Let serious events affect later choices.`;
|
|
935
|
+
function systemPrompt(role, schema, example) {
|
|
936
|
+
return `Role:\n${role}\nPrinciples:\n${WORLD_PRINCIPLES}\nFields:\n${responseFieldLines(schema)}\nExample:\n${example}`;
|
|
937
|
+
}
|
|
938
|
+
// One Game Master rules on both an actor's attempt and a happening that fell due; the input says which.
|
|
939
|
+
export const GAME_MASTER_SYSTEM = systemPrompt('You are the Game Master of a living world. Rule on an actor\'s attempt, which reaches only who and what is here, or a due happening. Tell the actor what they perceive, including what they read or hear, word for word, and add no act, word or feeling they did not state. Represent each lasting result with the fitting kind and typed slots; request a kind when none fits.', attemptResponseSchema, '{"outcome":"partial","stateKindFit":"complete","stateUpdates":[{"kind":"entity.create","values":{"condition":"cracked","entityId":"lamp-id","initialProperties":{},"kindId":"item","name":"Oil lamp","personId":"actor-id","placeId":"place-id"}}]}');
|
|
940
|
+
export const SPEECH_IMPACT_SYSTEM = systemPrompt('Assess one spoken line: its impact, the pressure it puts on listeners, and whether it carries. Never decide anyone\'s response.', speechImpactResponseSchema, '{"importance":3,"pressure":null,"loud":false}');
|
|
941
|
+
export const CHARACTER_REACTION_SYSTEM = systemPrompt('You are the named character. Respond to happened.pressure when it exists, then choose what you say or do now. Say nothing you already said.', reactionResponseSchema, '{"say":"I need time to think.","act":null,"request":null,"endConversation":false,"plan":{"intent":"Check the figures alone","seenDoing":"reading at the window","placeId":null,"targetId":null,"inMinutes":40}}');
|
|
942
|
+
export const CHARACTER_PLAN_SYSTEM = systemPrompt('You are the named character. What you were doing has ended, or something needs your decision. Live a whole day: eat, rest, go home when your day ends, and stop waiting when it brings nothing.', planResponseSchema, '{"action":"wait","placeId":null,"targetId":null,"text":null,"request":null,"nextPlan":{"intent":"Finish the report","seenDoing":"typing at the desk","placeId":null,"targetId":null,"inMinutes":45}}');
|
|
943
|
+
export const CHARACTER_MESSAGE_SYSTEM = systemPrompt('You are the named character answering a private message. Treat its claims as claims, then decide whether to reply and what you believe.', messageReplyResponseSchema, '{"reply":null,"belief":null,"plan":{"intent":"Find the sender","seenDoing":"heading for the stairs","placeId":null,"targetId":null,"inMinutes":10}}');
|
|
944
|
+
export const NARRATOR_EPILOGUE_SYSTEM = systemPrompt('Write an ending card from recorded outcomes. Use dry, kind humor and keep unresolved matters unresolved.', epilogueResponseSchema, '{"title":"The Long Afternoon","verdict":"The day ended.","highlights":["They went home."],"cast":[],"closing":"Tomorrow remained."}');
|