agents-city 0.3.0-beta.21
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/.claude-plugin/marketplace.json +16 -0
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.es.md +2021 -0
- package/README.md +1999 -0
- package/benchmarks/committee/README.md +39 -0
- package/benchmarks/committee/metrics.py +51 -0
- package/benchmarks/committee/run.py +64 -0
- package/benchmarks/committee/traces/01-single.json +8 -0
- package/benchmarks/committee/traces/02-mesh.json +16 -0
- package/benchmarks/committee/traces/03-chair.json +15 -0
- package/benchmarks/latency/README.md +35 -0
- package/benchmarks/latency/fake-claude-cli.mjs +119 -0
- package/benchmarks/latency/fake-native-server.mjs +340 -0
- package/benchmarks/latency/fake-tui.py +91 -0
- package/benchmarks/latency/live.py +380 -0
- package/benchmarks/stress/README.md +20 -0
- package/benchmarks/stress/run.py +487 -0
- package/bin/agents +4 -0
- package/bin/agents-city.js +116 -0
- package/bin/benchmark +17 -0
- package/bin/bus +6 -0
- package/bin/cities +3 -0
- package/bin/city +86 -0
- package/bin/committee +6 -0
- package/bin/demo +200 -0
- package/bin/exit +5 -0
- package/bin/hall +73 -0
- package/bin/hall.html +426 -0
- package/bin/logs +3 -0
- package/bin/report.py +14 -0
- package/bin/reset +3 -0
- package/bin/road +3 -0
- package/bin/seat +10 -0
- package/bin/serve.py +1428 -0
- package/bin/setup.py +753 -0
- package/bin/skills +3 -0
- package/bin/test +131 -0
- package/bin/test-adapter.py +183 -0
- package/bin/test-admision.py +99 -0
- package/bin/test-avatar.py +58 -0
- package/bin/test-benchmark.py +40 -0
- package/bin/test-broker.py +334 -0
- package/bin/test-cage.py +206 -0
- package/bin/test-card.py +402 -0
- package/bin/test-channel.py +379 -0
- package/bin/test-cities.py +453 -0
- package/bin/test-claude-runtime.py +252 -0
- package/bin/test-committee.py +340 -0
- package/bin/test-contracts.py +544 -0
- package/bin/test-crecimiento.py +164 -0
- package/bin/test-demo.py +308 -0
- package/bin/test-doctor.py +106 -0
- package/bin/test-domains.py +151 -0
- package/bin/test-evidencia.py +50 -0
- package/bin/test-exit.py +128 -0
- package/bin/test-hall-protocol.py +93 -0
- package/bin/test-launch.py +169 -0
- package/bin/test-live-feed.py +508 -0
- package/bin/test-pairing.py +99 -0
- package/bin/test-parcels.py +283 -0
- package/bin/test-runtime-failures.py +252 -0
- package/bin/test-runtime-ui.py +488 -0
- package/bin/test-runtime.py +268 -0
- package/bin/test-rutas.py +80 -0
- package/bin/test-seat.py +1386 -0
- package/bin/test-security.py +150 -0
- package/bin/test-serve.py +1057 -0
- package/bin/test-stress.py +64 -0
- package/bin/test-widgets.py +143 -0
- package/bin/test-workspace.py +188 -0
- package/bin/testlib.py +95 -0
- package/bin/tokens.py +14 -0
- package/bus/scripts/deploy.sh +60 -0
- package/bus/scripts/mint-token.sh +68 -0
- package/bus/scripts/setup-dev.sh +79 -0
- package/bus/scripts/test-channel.ts +8 -0
- package/bus/scripts/test-hub.ts +266 -0
- package/bus/scripts/test-local.sh +40 -0
- package/bus/scripts/test-queue.ts +132 -0
- package/bus/worker/package-lock.json +1971 -0
- package/bus/worker/package.json +17 -0
- package/bus/worker/src/index.ts +485 -0
- package/bus/worker/tsconfig.json +13 -0
- package/bus/worker/wrangler.toml +32 -0
- package/city/oven/README.md +59 -0
- package/city/oven/collect.py +43 -0
- package/city/oven/oven.html +4 -0
- package/city/scripts/history.py +120 -0
- package/city/scripts/seed.py +442 -0
- package/city/web/assets/modelos/License.txt +28 -0
- package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/assets/modelos/building-a.glb +0 -0
- package/city/web/assets/modelos/building-b.glb +0 -0
- package/city/web/assets/modelos/building-c.glb +0 -0
- package/city/web/assets/modelos/building-d.glb +0 -0
- package/city/web/assets/modelos/building-e.glb +0 -0
- package/city/web/assets/modelos/building-f.glb +0 -0
- package/city/web/assets/modelos/building-g.glb +0 -0
- package/city/web/assets/modelos/building-h.glb +0 -0
- package/city/web/assets/sprites/building-a.png +0 -0
- package/city/web/assets/sprites/building-b.png +0 -0
- package/city/web/assets/sprites/building-c.png +0 -0
- package/city/web/assets/sprites/building-d.png +0 -0
- package/city/web/assets/sprites/building-e.png +0 -0
- package/city/web/assets/sprites/building-f.png +0 -0
- package/city/web/assets/sprites/building-g.png +0 -0
- package/city/web/assets/sprites/building-h.png +0 -0
- package/city/web/assets/sprites/building-i.png +0 -0
- package/city/web/assets/sprites/building-j.png +0 -0
- package/city/web/assets/sprites/building-k.png +0 -0
- package/city/web/assets/sprites/building-l.png +0 -0
- package/city/web/assets/sprites/building-m.png +0 -0
- package/city/web/assets/sprites/building-n.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
- package/city/web/assets/sprites/catalogo.json +47 -0
- package/city/web/assets/sprites/detail-awning-wide.png +0 -0
- package/city/web/assets/sprites/detail-awning.png +0 -0
- package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
- package/city/web/assets/sprites/detail-overhang.png +0 -0
- package/city/web/assets/sprites/detail-parasol-a.png +0 -0
- package/city/web/assets/sprites/detail-parasol-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-c.png +0 -0
- package/city/web/assets/sprites/low-detail-building-d.png +0 -0
- package/city/web/assets/sprites/low-detail-building-e.png +0 -0
- package/city/web/assets/sprites/low-detail-building-f.png +0 -0
- package/city/web/assets/sprites/low-detail-building-g.png +0 -0
- package/city/web/assets/sprites/low-detail-building-h.png +0 -0
- package/city/web/assets/sprites/low-detail-building-i.png +0 -0
- package/city/web/assets/sprites/low-detail-building-j.png +0 -0
- package/city/web/assets/sprites/low-detail-building-k.png +0 -0
- package/city/web/assets/sprites/low-detail-building-l.png +0 -0
- package/city/web/assets/sprites/low-detail-building-m.png +0 -0
- package/city/web/assets/sprites/low-detail-building-n.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/assets/sprites/medidas.json +248 -0
- package/city/web/dist/city.js +939 -0
- package/city/web/dist/index.html +294 -0
- package/city/web/dist/modelos/License.txt +28 -0
- package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/dist/modelos/building-a.glb +0 -0
- package/city/web/dist/modelos/building-b.glb +0 -0
- package/city/web/dist/modelos/building-c.glb +0 -0
- package/city/web/dist/modelos/building-d.glb +0 -0
- package/city/web/dist/modelos/building-e.glb +0 -0
- package/city/web/dist/modelos/building-f.glb +0 -0
- package/city/web/dist/modelos/building-g.glb +0 -0
- package/city/web/dist/modelos/building-h.glb +0 -0
- package/city/web/dist/sprites/building-a.png +0 -0
- package/city/web/dist/sprites/building-b.png +0 -0
- package/city/web/dist/sprites/building-c.png +0 -0
- package/city/web/dist/sprites/building-d.png +0 -0
- package/city/web/dist/sprites/building-e.png +0 -0
- package/city/web/dist/sprites/building-f.png +0 -0
- package/city/web/dist/sprites/building-g.png +0 -0
- package/city/web/dist/sprites/building-h.png +0 -0
- package/city/web/dist/sprites/building-i.png +0 -0
- package/city/web/dist/sprites/building-j.png +0 -0
- package/city/web/dist/sprites/building-k.png +0 -0
- package/city/web/dist/sprites/building-l.png +0 -0
- package/city/web/dist/sprites/building-m.png +0 -0
- package/city/web/dist/sprites/building-n.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
- package/city/web/dist/sprites/catalogo.json +47 -0
- package/city/web/dist/sprites/detail-awning-wide.png +0 -0
- package/city/web/dist/sprites/detail-awning.png +0 -0
- package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
- package/city/web/dist/sprites/detail-overhang.png +0 -0
- package/city/web/dist/sprites/detail-parasol-a.png +0 -0
- package/city/web/dist/sprites/detail-parasol-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-c.png +0 -0
- package/city/web/dist/sprites/low-detail-building-d.png +0 -0
- package/city/web/dist/sprites/low-detail-building-e.png +0 -0
- package/city/web/dist/sprites/low-detail-building-f.png +0 -0
- package/city/web/dist/sprites/low-detail-building-g.png +0 -0
- package/city/web/dist/sprites/low-detail-building-h.png +0 -0
- package/city/web/dist/sprites/low-detail-building-i.png +0 -0
- package/city/web/dist/sprites/low-detail-building-j.png +0 -0
- package/city/web/dist/sprites/low-detail-building-k.png +0 -0
- package/city/web/dist/sprites/low-detail-building-l.png +0 -0
- package/city/web/dist/sprites/low-detail-building-m.png +0 -0
- package/city/web/dist/sprites/low-detail-building-n.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/dist/sprites/medidas.json +248 -0
- package/city/web/dist-hall/hall.js +264 -0
- package/city/web/index.html +294 -0
- package/city/web/package.json +18 -0
- package/city/web/sella.py +21 -0
- package/city/web/src/activity-actors.ts +47 -0
- package/city/web/src/activity.ts +172 -0
- package/city/web/src/ayuntamiento.ts +580 -0
- package/city/web/src/draw.ts +640 -0
- package/city/web/src/game-speech.ts +79 -0
- package/city/web/src/hall.ts +1867 -0
- package/city/web/src/main.ts +2225 -0
- package/city/web/src/oven.ts +124 -0
- package/city/web/src/people.ts +244 -0
- package/city/web/src/presencia.ts +193 -0
- package/city/web/src/puertas.ts +180 -0
- package/city/web/src/repo-roles.ts +59 -0
- package/city/worker/package.json +16 -0
- package/city/worker/schema.sql +117 -0
- package/city/worker/src/index.ts +702 -0
- package/city/worker/src/square.ts +218 -0
- package/city/worker/wrangler.toml +58 -0
- package/demo/ada.md +50 -0
- package/demo/bruno.md +62 -0
- package/demo/camila.md +56 -0
- package/demo/city/ada.md +40 -0
- package/demo/city/city.yml +8 -0
- package/demo/city/parcels.yml +15 -0
- package/demo/city/roads.json +4 -0
- package/demo/city/units.yml +10 -0
- package/demo/clinica/city.yml +8 -0
- package/demo/clinica/parcels.yml +15 -0
- package/demo/clinica/roads.json +4 -0
- package/demo/clinica/units.yml +9 -0
- package/demo/clinica/vera.md +46 -0
- package/demo/dante.md +54 -0
- package/demo/despacho/city.yml +8 -0
- package/demo/despacho/marta.md +46 -0
- package/demo/despacho/parcels.yml +15 -0
- package/demo/despacho/roads.json +4 -0
- package/demo/despacho/units.yml +9 -0
- package/demo/elsa.md +50 -0
- package/demo/farid.md +52 -0
- package/demo/greta.md +50 -0
- package/demo/hugo.md +49 -0
- package/demo/iris.md +49 -0
- package/demo/jonas.md +51 -0
- package/demo/kira.md +51 -0
- package/demo/luca.md +49 -0
- package/demo/parcels.yml +92 -0
- package/demo/seed.py +20 -0
- package/demo/show.py +161 -0
- package/demo/stories.py +824 -0
- package/demo/units.yml +33 -0
- package/docs/agents-first.md +99 -0
- package/docs/glossary.md +55 -0
- package/docs/map-live-layers.md +73 -0
- package/docs/security.md +231 -0
- package/docs/self-host.md +151 -0
- package/docs/testing.md +205 -0
- package/package.json +73 -0
- package/plugin/.claude-plugin/plugin.json +62 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/channel/activity-cli.ts +16 -0
- package/plugin/channel/adapter-prompts.ts +113 -0
- package/plugin/channel/adapter.js +4292 -0
- package/plugin/channel/adapter.ts +133 -0
- package/plugin/channel/bus.js +20188 -0
- package/plugin/channel/bus.ts +131 -0
- package/plugin/channel/city-config.ts +125 -0
- package/plugin/channel/claude-channel.ts +58 -0
- package/plugin/channel/cli-args.ts +75 -0
- package/plugin/channel/client.js +4200 -0
- package/plugin/channel/client.ts +38 -0
- package/plugin/channel/committee/activity.ts +256 -0
- package/plugin/channel/committee/collection.ts +154 -0
- package/plugin/channel/committee/decision.ts +175 -0
- package/plugin/channel/committee/floor.ts +135 -0
- package/plugin/channel/committee/guards.ts +30 -0
- package/plugin/channel/committee/history.ts +38 -0
- package/plugin/channel/committee/render.ts +102 -0
- package/plugin/channel/committee/service.ts +72 -0
- package/plugin/channel/committee/storage.ts +92 -0
- package/plugin/channel/committee/types.ts +163 -0
- package/plugin/channel/committee/view.ts +63 -0
- package/plugin/channel/committee-cli.ts +109 -0
- package/plugin/channel/delivery-metrics.ts +61 -0
- package/plugin/channel/delivery-queue.ts +118 -0
- package/plugin/channel/hub/activity-controller.ts +72 -0
- package/plugin/channel/hub/activity-feed.ts +169 -0
- package/plugin/channel/hub/committee-controller.ts +60 -0
- package/plugin/channel/hub/connections.ts +48 -0
- package/plugin/channel/hub/diagnostics.ts +67 -0
- package/plugin/channel/hub/envelope-validity.ts +109 -0
- package/plugin/channel/hub/envelopes.ts +66 -0
- package/plugin/channel/hub/lifecycle.ts +91 -0
- package/plugin/channel/hub/local-roads.ts +89 -0
- package/plugin/channel/hub/remote-roads.ts +165 -0
- package/plugin/channel/hub/road-controller.ts +99 -0
- package/plugin/channel/hub-client.ts +149 -0
- package/plugin/channel/local-hub.js +5995 -0
- package/plugin/channel/local-hub.ts +349 -0
- package/plugin/channel/map-reporter.ts +48 -0
- package/plugin/channel/package-lock.json +1731 -0
- package/plugin/channel/package.json +19 -0
- package/plugin/channel/protocol.ts +98 -0
- package/plugin/channel/road-cli.ts +18 -0
- package/plugin/channel/run.sh +15 -0
- package/plugin/channel/runtime/claude.ts +458 -0
- package/plugin/channel/runtime/codex-config.ts +77 -0
- package/plugin/channel/runtime/codex.ts +669 -0
- package/plugin/channel/runtime/command.ts +62 -0
- package/plugin/channel/runtime/factory.ts +15 -0
- package/plugin/channel/runtime/json-rpc.ts +154 -0
- package/plugin/channel/runtime/kimi.ts +277 -0
- package/plugin/channel/runtime/opencode.ts +248 -0
- package/plugin/channel/runtime/process.ts +84 -0
- package/plugin/channel/runtime/types.ts +45 -0
- package/plugin/channel/runtime-files.ts +73 -0
- package/plugin/channel/runtime-gateway.js +6396 -0
- package/plugin/channel/runtime-gateway.ts +358 -0
- package/plugin/channel/runtime-metrics.ts +52 -0
- package/plugin/channel/runtime-subscription.ts +180 -0
- package/plugin/channel/terminal-delivery.ts +131 -0
- package/plugin/channel/untrusted.ts +60 -0
- package/plugin/commands/committee.md +31 -0
- package/plugin/commands/exit.md +32 -0
- package/plugin/commands/goals.md +20 -0
- package/plugin/commands/join.md +18 -0
- package/plugin/commands/notice.md +20 -0
- package/plugin/commands/propose.md +19 -0
- package/plugin/commands/round.md +17 -0
- package/plugin/commands/session.md +22 -0
- package/plugin/commands/settings.md +25 -0
- package/plugin/commands/setup.md +21 -0
- package/plugin/commands/team.md +23 -0
- package/plugin/domains/custom.md +27 -0
- package/plugin/domains/finance.md +31 -0
- package/plugin/domains/healthcare.md +34 -0
- package/plugin/domains/legal.md +31 -0
- package/plugin/domains/marketing.md +33 -0
- package/plugin/domains/operations.md +31 -0
- package/plugin/domains/research.md +32 -0
- package/plugin/domains/sales.md +30 -0
- package/plugin/domains/software.md +37 -0
- package/plugin/hooks/activity.sh +6 -0
- package/plugin/hooks/digging.sh +68 -0
- package/plugin/hooks/growth.sh +42 -0
- package/plugin/hooks/hooks.json +106 -0
- package/plugin/hooks/notice-on-pr.sh +35 -0
- package/plugin/hooks/notice-on-stop.sh +80 -0
- package/plugin/hooks/notice-pending.sh +58 -0
- package/plugin/hooks/solo-en-ciudad.sh +23 -0
- package/plugin/hooks/tokens.sh +47 -0
- package/plugin/roles/examples/account-executive.md +25 -0
- package/plugin/roles/examples/ai-manager.md +34 -0
- package/plugin/roles/examples/associate.md +24 -0
- package/plugin/roles/examples/brand-lead.md +20 -0
- package/plugin/roles/examples/cfo.md +20 -0
- package/plugin/roles/examples/city-lead.md +25 -0
- package/plugin/roles/examples/clinical-director.md +26 -0
- package/plugin/roles/examples/clinical-ops.md +25 -0
- package/plugin/roles/examples/clinician.md +25 -0
- package/plugin/roles/examples/compliance.md +27 -0
- package/plugin/roles/examples/content.md +24 -0
- package/plugin/roles/examples/controller.md +27 -0
- package/plugin/roles/examples/cpto.md +41 -0
- package/plugin/roles/examples/customer-success.md +25 -0
- package/plugin/roles/examples/data-engineer.md +37 -0
- package/plugin/roles/examples/data.md +60 -0
- package/plugin/roles/examples/dev.md +37 -0
- package/plugin/roles/examples/devops.md +39 -0
- package/plugin/roles/examples/enablement.md +24 -0
- package/plugin/roles/examples/ethics.md +24 -0
- package/plugin/roles/examples/fin-analytics.md +27 -0
- package/plugin/roles/examples/health-compliance.md +25 -0
- package/plugin/roles/examples/health-data.md +26 -0
- package/plugin/roles/examples/knowledge.md +25 -0
- package/plugin/roles/examples/lifecycle.md +26 -0
- package/plugin/roles/examples/llm-engineer.md +34 -0
- package/plugin/roles/examples/managing-partner.md +20 -0
- package/plugin/roles/examples/methods.md +23 -0
- package/plugin/roles/examples/operations-lead.md +23 -0
- package/plugin/roles/examples/ops.md +24 -0
- package/plugin/roles/examples/patient-safety.md +26 -0
- package/plugin/roles/examples/performance.md +27 -0
- package/plugin/roles/examples/po.md +36 -0
- package/plugin/roles/examples/process-owner.md +23 -0
- package/plugin/roles/examples/product-design.md +39 -0
- package/plugin/roles/examples/program-manager.md +24 -0
- package/plugin/roles/examples/quality.md +25 -0
- package/plugin/roles/examples/research-director.md +24 -0
- package/plugin/roles/examples/research-ops.md +25 -0
- package/plugin/roles/examples/researcher.md +25 -0
- package/plugin/roles/examples/revenue-lead.md +25 -0
- package/plugin/roles/examples/revops.md +26 -0
- package/plugin/roles/examples/seo.md +27 -0
- package/plugin/roles/examples/specialist.md +24 -0
- package/plugin/scripts/admision.py +165 -0
- package/plugin/scripts/apaga.py +232 -0
- package/plugin/scripts/avatar.py +211 -0
- package/plugin/scripts/broker.py +531 -0
- package/plugin/scripts/cage.py +252 -0
- package/plugin/scripts/capabilities.py +197 -0
- package/plugin/scripts/card.py +312 -0
- package/plugin/scripts/cities.py +520 -0
- package/plugin/scripts/city-env.sh +90 -0
- package/plugin/scripts/city-runtime.sh +87 -0
- package/plugin/scripts/city-session.sh +526 -0
- package/plugin/scripts/city_env.py +67 -0
- package/plugin/scripts/crecimiento.py +140 -0
- package/plugin/scripts/deliberations.py +48 -0
- package/plugin/scripts/doctor.py +172 -0
- package/plugin/scripts/domains.py +201 -0
- package/plugin/scripts/evidencia.py +64 -0
- package/plugin/scripts/find-repos.sh +111 -0
- package/plugin/scripts/gh.py +159 -0
- package/plugin/scripts/hall_protocol.py +147 -0
- package/plugin/scripts/hook_activity.py +139 -0
- package/plugin/scripts/launch.py +94 -0
- package/plugin/scripts/logs.py +98 -0
- package/plugin/scripts/pairing.py +185 -0
- package/plugin/scripts/parcels.py +132 -0
- package/plugin/scripts/read-card.py +51 -0
- package/plugin/scripts/report.py +201 -0
- package/plugin/scripts/reset.py +180 -0
- package/plugin/scripts/roads.py +230 -0
- package/plugin/scripts/roles.py +274 -0
- package/plugin/scripts/runtime_log.py +69 -0
- package/plugin/scripts/runtime_processes.py +132 -0
- package/plugin/scripts/rutas.py +83 -0
- package/plugin/scripts/seat.py +1349 -0
- package/plugin/scripts/tokens.py +194 -0
- package/plugin/scripts/trust-repos.py +59 -0
- package/plugin/scripts/ui.py +280 -0
- package/plugin/scripts/units.py +80 -0
- package/plugin/scripts/workspace.py +364 -0
- package/plugin/skills/city/SKILL.md +191 -0
- package/templates/blank.md +39 -0
- package/templates/finance.md +44 -0
- package/templates/legal.md +45 -0
- package/templates/marketing.md +48 -0
- package/templates/product.md +45 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drawing the city.
|
|
3
|
+
*
|
|
4
|
+
* Three rules hold this together as a city rather than an isometric bar chart:
|
|
5
|
+
* 1. Three tones per volume — light roof, mid left face, dark right. Without
|
|
6
|
+
* that, a single-colour cube is a smudge.
|
|
7
|
+
* 2. A shadow on the ground. It is what sets the house down; without it the
|
|
8
|
+
* house floats.
|
|
9
|
+
* 3. Lit windows where there is activity. A city with no lights is dead.
|
|
10
|
+
*
|
|
11
|
+
* The fallback path is all Graphics — no atlas, no image editor — so changing
|
|
12
|
+
* how a house looks is changing ten lines.
|
|
13
|
+
*/
|
|
14
|
+
import { Container, Graphics, Sprite, Text, TextStyle, Texture } from 'pixi.js';
|
|
15
|
+
|
|
16
|
+
export const TW = 62,
|
|
17
|
+
TH = 31; // the isometric tile
|
|
18
|
+
export const PISO = 9;
|
|
19
|
+
/**
|
|
20
|
+
* Distance between plots, in tiles. Above one so the houses have air around
|
|
21
|
+
* them: at exactly one tile a district reads as a single mass, and the point of
|
|
22
|
+
* drawing parcels instead of repos is that you can tell them apart.
|
|
23
|
+
*
|
|
24
|
+
* Exported because three things have to agree on it — the tiles, the fence, and
|
|
25
|
+
* where the houses stand. They used to each carry their own 1.3.
|
|
26
|
+
*/
|
|
27
|
+
export const PASO = 1.95;
|
|
28
|
+
|
|
29
|
+
export const iso = (cx: number, cy: number) => ({
|
|
30
|
+
x: ((cx - cy) * TW) / 2,
|
|
31
|
+
y: ((cx + cy) * TH) / 2,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Lighten or darken a whole colour. All three faces come from here. */
|
|
35
|
+
export function tono(c: number, k: number): number {
|
|
36
|
+
const r = (c >> 16) & 255,
|
|
37
|
+
g = (c >> 8) & 255,
|
|
38
|
+
b = c & 255;
|
|
39
|
+
const f = (v: number) =>
|
|
40
|
+
Math.max(0, Math.min(255, Math.round(k > 0 ? v + (255 - v) * k : v * (1 + k))));
|
|
41
|
+
return (f(r) << 16) | (f(g) << 8) | f(b);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DatosCasa {
|
|
45
|
+
pisos: number;
|
|
46
|
+
andamios: number;
|
|
47
|
+
andamio_viejo: number;
|
|
48
|
+
grieta: number;
|
|
49
|
+
actividad30: number;
|
|
50
|
+
unidad: string;
|
|
51
|
+
/** The agent's kind, when the Hall has told the map: it picks the family. */
|
|
52
|
+
clase?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── Baked sprites ──────────────────────────────────────────────────────────
|
|
56
|
+
* The buildings are isometric PNGs baked from Kenney's 3D models (CC0) under one
|
|
57
|
+
* light and one angle. They were baked in light grey so the colour comes from
|
|
58
|
+
* each district's tint: one model serves every district instead of baking the
|
|
59
|
+
* same building once per colour.
|
|
60
|
+
*/
|
|
61
|
+
export interface Medida {
|
|
62
|
+
alto: number;
|
|
63
|
+
ancho: number;
|
|
64
|
+
ancla: number;
|
|
65
|
+
pxUnidad: number;
|
|
66
|
+
}
|
|
67
|
+
let TEX: Record<string, Texture> = {};
|
|
68
|
+
let MED: Record<string, Medida> = {};
|
|
69
|
+
let ORDENADOS: string[] = []; // commercial towers, shortest first: the code family
|
|
70
|
+
let BAJOS: string[] = []; // low-detail blocks: the knowledge/coordinator family
|
|
71
|
+
|
|
72
|
+
export async function cargaSprites(): Promise<boolean> {
|
|
73
|
+
try {
|
|
74
|
+
MED = await (await fetch('/sprites/medidas.json')).json();
|
|
75
|
+
// Two building families from the same baked kit. The commercial towers are
|
|
76
|
+
// what a code agent's parcel wears; the low-detail blocks — lower, wider,
|
|
77
|
+
// reading as archive and civic rather than office — dress the agents whose
|
|
78
|
+
// work is not pull requests. The kit had both baked all along; the map
|
|
79
|
+
// only ever used one.
|
|
80
|
+
ORDENADOS = Object.keys(MED)
|
|
81
|
+
.filter((k) => k.startsWith('building'))
|
|
82
|
+
.sort((a, b) => MED[a].alto - MED[b].alto);
|
|
83
|
+
BAJOS = Object.keys(MED)
|
|
84
|
+
.filter((k) => k.startsWith('low-detail-building'))
|
|
85
|
+
.sort((a, b) => MED[a].alto - MED[b].alto);
|
|
86
|
+
// A bare Image instead of Pixi's asset manager: with Assets.load the load
|
|
87
|
+
// hung without throwing, and a blank page with no exception is the hardest
|
|
88
|
+
// thing there is to diagnose.
|
|
89
|
+
await Promise.all(
|
|
90
|
+
[...ORDENADOS, ...BAJOS].map(async (k) => {
|
|
91
|
+
const img = new Image();
|
|
92
|
+
img.src = `/sprites/${k}.png`;
|
|
93
|
+
await img.decode();
|
|
94
|
+
TEX[k] = Texture.from(img);
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
return Object.keys(TEX).length > 0;
|
|
98
|
+
} catch {
|
|
99
|
+
// With no sprites the city is drawn with polygons: worse, but drawn.
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* How tall a house should look, in tiles, for the capital it holds.
|
|
106
|
+
*
|
|
107
|
+
* Continuous on purpose. The first version picked one of four baked models by
|
|
108
|
+
* band, which meant a house showed one or two heights across a whole replay:
|
|
109
|
+
* floors landed and nothing moved, and then a house jumped. Growth you cannot
|
|
110
|
+
* see is the one thing this map cannot afford.
|
|
111
|
+
*
|
|
112
|
+
* Logarithmic because capital is: the difference between 1 and 8 landings is
|
|
113
|
+
* worth more than the one between 900 and 907, and a linear scale would leave
|
|
114
|
+
* every small house invisible next to the monolith of the oldest repo.
|
|
115
|
+
*/
|
|
116
|
+
export function alturaDe(pisos: number): number {
|
|
117
|
+
// Capped, and not for looks: an uncapped log still reached seven tiles on the
|
|
118
|
+
// oldest repo, and a building that tall covers the district behind it and its
|
|
119
|
+
// own banner. A map you cannot read does not describe anything.
|
|
120
|
+
return pisos <= 0 ? 0 : Math.min(ALTO_MAX, 0.55 + Math.log2(pisos + 1) * 0.36);
|
|
121
|
+
}
|
|
122
|
+
export const ALTO_MAX = 4.2;
|
|
123
|
+
/** One height unit, in pixels: what a tile-high building measures on screen. */
|
|
124
|
+
export const ALTO_TILE = TW * 0.92;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Which baked building this parcel gets: the one closest to the height its
|
|
128
|
+
* capital asks for, and stable for the parcel's name.
|
|
129
|
+
*
|
|
130
|
+
* By target height and not by band, so the stretch that follows is always small.
|
|
131
|
+
* The band version had to squash or stretch a model by up to two and a half to
|
|
132
|
+
* reach the right height, and a Kenney tower at 2.4× vertical does not read as a
|
|
133
|
+
* building any more.
|
|
134
|
+
*
|
|
135
|
+
* Ties are broken by hashing the id, over the three nearest models, so two
|
|
136
|
+
* parcels of the same size are not the same silhouette — and the same parcel is
|
|
137
|
+
* always the same one, which matters more: a house that changed shape between
|
|
138
|
+
* two loads would break the one thing a map has to do.
|
|
139
|
+
*/
|
|
140
|
+
function elModelo(id: string, pisos: number, clase?: string): string | null {
|
|
141
|
+
// The family is the agent's kind, made visible: knowledge and coordinator
|
|
142
|
+
// parcels wear the low blocks. An unknown kind — or a map that was never
|
|
143
|
+
// told any — keeps today's towers.
|
|
144
|
+
const familia =
|
|
145
|
+
(clase === 'knowledge' || clase === 'coordinator') && BAJOS.length ? BAJOS : ORDENADOS;
|
|
146
|
+
if (!familia.length) return null;
|
|
147
|
+
const objetivo = alturaDe(pisos);
|
|
148
|
+
const cerca = [...familia]
|
|
149
|
+
.sort((a, b) => Math.abs(MED[a].alto - objetivo) - Math.abs(MED[b].alto - objetivo))
|
|
150
|
+
.slice(0, 3);
|
|
151
|
+
let n = 0;
|
|
152
|
+
for (let j = 0; j < id.length; j++) n = (n * 31 + id.charCodeAt(j)) | 0;
|
|
153
|
+
return cerca[Math.abs(n) % cerca.length];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A drawn house. `sp`, `medida` and `escala` are only there on the sprite path,
|
|
158
|
+
* and they are what lets the house be grown later without being rebuilt; the
|
|
159
|
+
* polygon fallback has to be redrawn, and says so by leaving them out.
|
|
160
|
+
*/
|
|
161
|
+
export interface Casa {
|
|
162
|
+
g: Container;
|
|
163
|
+
alto: number;
|
|
164
|
+
plantas: number;
|
|
165
|
+
sp?: Sprite;
|
|
166
|
+
medida?: Medida;
|
|
167
|
+
escala?: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The model a parcel should be wearing at this size, or null when it is already
|
|
172
|
+
* wearing it.
|
|
173
|
+
*
|
|
174
|
+
* A building is not chosen once and kept: it is chosen for the height the parcel
|
|
175
|
+
* asks for *now*. The first version picked it when the house appeared — with one
|
|
176
|
+
* floor — and never revisited it, so a house grew until the stretch hit its limit
|
|
177
|
+
* and then stopped, however much landed afterwards. On screen that is a city that
|
|
178
|
+
* does not grow, punctuated by jumps whenever something else forced a redraw.
|
|
179
|
+
*/
|
|
180
|
+
export function reviste(c: Casa, id: string, pisos: number, clase?: string): boolean {
|
|
181
|
+
if (!c.sp || !c.medida) return false;
|
|
182
|
+
const f = factorDe(pisos, c.medida);
|
|
183
|
+
// Only when the current model is being stretched or squashed out of shape.
|
|
184
|
+
if (f > 0.82 && f < 1.22) return false;
|
|
185
|
+
const nombre = elModelo(id, pisos, clase);
|
|
186
|
+
if (!nombre || !TEX[nombre] || MED[nombre] === c.medida) return false;
|
|
187
|
+
|
|
188
|
+
const m = MED[nombre];
|
|
189
|
+
const altoAhora = c.medida.alto * (c.sp.scale.y / (c.escala ?? 1)); // in tiles
|
|
190
|
+
c.sp.texture = TEX[nombre];
|
|
191
|
+
c.sp.anchor.set(0.5, m.ancla);
|
|
192
|
+
c.medida = m;
|
|
193
|
+
c.escala = (TW / m.pxUnidad) * 0.92;
|
|
194
|
+
// Keep the height it has RIGHT NOW: the silhouette changes, the size does not
|
|
195
|
+
// jump. Whatever tween is running then carries it to the new target.
|
|
196
|
+
c.sp.scale.set(c.escala, c.escala * Math.max(0.5, altoAhora / m.alto));
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Una casa: sombra, cuerpo a tres tonos, tejado con teja, ventanas y extras. */
|
|
201
|
+
export function casa(col: number, d: DatosCasa, id = ''): Casa {
|
|
202
|
+
const modelo = elModelo(id, d.pisos, d.clase);
|
|
203
|
+
if (modelo && TEX[modelo]) return casaSprite(col, d, modelo);
|
|
204
|
+
return casaPoligonos(col, d);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** With a sprite: the baked building, tinted with its district's colour. */
|
|
208
|
+
function casaSprite(col: number, d: DatosCasa, modelo: string): Casa {
|
|
209
|
+
const g = new Container();
|
|
210
|
+
const m = MED[modelo];
|
|
211
|
+
const sp = new Sprite(TEX[modelo]);
|
|
212
|
+
sp.anchor.set(0.5, m.ancla);
|
|
213
|
+
const escala = (TW / m.pxUnidad) * 0.92;
|
|
214
|
+
// The model gives the silhouette; the stretch gives the exact height. Both are
|
|
215
|
+
// needed: without the stretch growth is invisible, and without the model every
|
|
216
|
+
// building would be the same one squashed or stretched absurdly.
|
|
217
|
+
const estirado = factorDe(d.pisos, m);
|
|
218
|
+
sp.scale.set(escala, escala * estirado);
|
|
219
|
+
sp.position.set(0, TH / 2);
|
|
220
|
+
// Tint multiplies, so a dark colour switches the building off: it is lightened
|
|
221
|
+
// first, so the district reads and the volume survives.
|
|
222
|
+
sp.tint = tono(col, 0.34);
|
|
223
|
+
const alto = m.alto * estirado * m.pxUnidad * escala;
|
|
224
|
+
|
|
225
|
+
g.addChild(
|
|
226
|
+
new Graphics()
|
|
227
|
+
.ellipse(0, TH * 0.55, TW * 0.34, TH * 0.3)
|
|
228
|
+
.fill({ color: 0x000000, alpha: 0.34 }),
|
|
229
|
+
);
|
|
230
|
+
g.addChild(sp);
|
|
231
|
+
|
|
232
|
+
// A warm glow if the parcel moved this month: a city with lights reads as
|
|
233
|
+
// alive, and without them it looks abandoned.
|
|
234
|
+
if (d.actividad30 > 0) {
|
|
235
|
+
const luz = new Graphics()
|
|
236
|
+
.ellipse(0, TH / 2 - alto * 0.45, TW * 0.3, alto * 0.42)
|
|
237
|
+
.fill({ color: 0xffd98a, alpha: Math.min(0.16, 0.03 + Math.log2(d.actividad30 + 1) * 0.02) });
|
|
238
|
+
luz.blendMode = 'add';
|
|
239
|
+
g.addChild(luz);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
extras(g, col, d, alto, TW * 0.3);
|
|
243
|
+
// `sp` and `m` come out so a house can be grown without being rebuilt: tearing
|
|
244
|
+
// the sprite down every time a floor lands made the city blink.
|
|
245
|
+
return { g, alto, plantas: Math.round(m.alto * estirado), sp, medida: m, escala };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function casaPoligonos(col: number, d: DatosCasa): Casa {
|
|
249
|
+
const g = new Container();
|
|
250
|
+
const plantas = Math.max(1, Math.min(24, Math.round(alturaDe(d.pisos) * 2.1)));
|
|
251
|
+
const alto = plantas * PISO;
|
|
252
|
+
const w = TW * 0.27,
|
|
253
|
+
h = TH * 0.27;
|
|
254
|
+
|
|
255
|
+
// Sombra: lo que hace que la casa se apoye en el suelo en vez de flotar.
|
|
256
|
+
g.addChild(
|
|
257
|
+
new Graphics()
|
|
258
|
+
.ellipse(w * 0.35, TH * 0.52, w * 1.5, h * 1.5)
|
|
259
|
+
.fill({ color: 0x000000, alpha: 0.28 }),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const cuerpo = new Graphics();
|
|
263
|
+
// cara izquierda (media) y derecha (oscura)
|
|
264
|
+
cuerpo
|
|
265
|
+
.poly([0, TH / 2, -w, TH / 2 - h, -w, TH / 2 - h - alto, 0, TH / 2 - alto])
|
|
266
|
+
.fill({ color: tono(col, -0.28) });
|
|
267
|
+
cuerpo
|
|
268
|
+
.poly([0, TH / 2, w, TH / 2 - h, w, TH / 2 - h - alto, 0, TH / 2 - alto])
|
|
269
|
+
.fill({ color: tono(col, -0.52) });
|
|
270
|
+
g.addChild(cuerpo);
|
|
271
|
+
|
|
272
|
+
// Windows: one row per storey, lit according to the last month's activity.
|
|
273
|
+
const encendidas = Math.min(plantas, Math.ceil(Math.log2(d.actividad30 + 1) / 1.6));
|
|
274
|
+
const luz = new Graphics();
|
|
275
|
+
for (let i = 0; i < plantas; i++) {
|
|
276
|
+
const y = TH / 2 - h * 0.5 - i * PISO - PISO * 0.55;
|
|
277
|
+
const on = i < encendidas;
|
|
278
|
+
const cl = on ? 0xffd98a : tono(col, -0.72);
|
|
279
|
+
luz.rect(-w * 0.62, y, w * 0.3, PISO * 0.34).fill({ color: cl, alpha: on ? 0.95 : 0.7 });
|
|
280
|
+
luz
|
|
281
|
+
.rect(w * 0.32, y + h * 0.5, w * 0.3, PISO * 0.34)
|
|
282
|
+
.fill({ color: cl, alpha: on ? 0.8 : 0.55 });
|
|
283
|
+
}
|
|
284
|
+
g.addChild(luz);
|
|
285
|
+
|
|
286
|
+
// A pitched roof, with two tile lines.
|
|
287
|
+
const cum = TH / 2 - 2 * h - alto;
|
|
288
|
+
const tejado = new Graphics()
|
|
289
|
+
.poly([0, TH / 2 - alto, -w, TH / 2 - h - alto, 0, cum, w, TH / 2 - h - alto])
|
|
290
|
+
.fill({ color: tono(col, 0.22) });
|
|
291
|
+
tejado.moveTo(-w * 0.5, TH / 2 - h * 0.5 - alto).lineTo(0, cum + h * 0.5);
|
|
292
|
+
tejado.moveTo(w * 0.5, TH / 2 - h * 0.5 - alto).lineTo(0, cum + h * 0.5);
|
|
293
|
+
tejado.stroke({ color: tono(col, -0.35), width: 0.8, alpha: 0.5 });
|
|
294
|
+
g.addChild(tejado);
|
|
295
|
+
|
|
296
|
+
extras(g, col, d, alto, w);
|
|
297
|
+
return { g, alto, plantas };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Dust at the foot of a house that just grew. Returns the motes to animate. */
|
|
301
|
+
export function polvo(g: Container): Graphics[] {
|
|
302
|
+
const motas: Graphics[] = [];
|
|
303
|
+
for (let i = 0; i < 4; i++) {
|
|
304
|
+
const p = new Graphics().circle(0, 0, 1.6 + i * 0.5).fill({ color: 0xd9cdb4, alpha: 0.5 });
|
|
305
|
+
p.position.set((i - 1.5) * 5, TH * 0.5);
|
|
306
|
+
g.addChild(p);
|
|
307
|
+
motas.push(p);
|
|
308
|
+
}
|
|
309
|
+
return motas;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Grow a house that is already on the map, to the height its new capital asks
|
|
314
|
+
* for. Returns false when it cannot (the polygon fallback, which has to be
|
|
315
|
+
* redrawn) so the caller knows to rebuild instead.
|
|
316
|
+
*
|
|
317
|
+
* This exists because the replay used to destroy and recreate every house that
|
|
318
|
+
* gained a floor, once per day. Same sprite, new object: the city flickered and
|
|
319
|
+
* nothing appeared to grow.
|
|
320
|
+
*/
|
|
321
|
+
export function estira(c: Casa, pisos: number): boolean {
|
|
322
|
+
if (!c.sp || !c.medida || !c.escala) return false;
|
|
323
|
+
c.sp.scale.set(c.escala, c.escala * factorDe(pisos, c.medida));
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** How much the baked model has to be stretched to stand at its proper height. */
|
|
328
|
+
export function factorDe(pisos: number, m: Medida): number {
|
|
329
|
+
// Narrow on purpose: the model is chosen for its height, so this only trims.
|
|
330
|
+
return Math.max(0.72, Math.min(1.5, alturaDe(pisos) / m.alto));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* What goes on top of a house, sprite or polygons alike. It lives apart because
|
|
335
|
+
* it is the same in both cases, and because it is the part carrying the news:
|
|
336
|
+
* the house says how much capital there is, this says what is happening to it.
|
|
337
|
+
*/
|
|
338
|
+
function extras(g: Container, col: number, d: DatosCasa, alto: number, w: number) {
|
|
339
|
+
const h = TH * 0.26;
|
|
340
|
+
const niveles = Math.max(1, Math.round(alto / PISO));
|
|
341
|
+
|
|
342
|
+
// Scaffolding: an open position. Amber when it has been stuck for more than
|
|
343
|
+
// two weeks. In the lab, always: that is a site open on purpose.
|
|
344
|
+
if (d.andamios > 0 || d.unidad === 'lab') {
|
|
345
|
+
const inm = d.andamio_viejo > 0 && d.unidad !== 'lab';
|
|
346
|
+
const a = new Graphics();
|
|
347
|
+
for (let i = 0; i <= niveles; i++)
|
|
348
|
+
a.moveTo(-w - 3, TH / 2 - i * PISO).lineTo(w + 3, TH / 2 - h - i * PISO);
|
|
349
|
+
a.moveTo(-w - 3, TH / 2).lineTo(-w - 3, TH / 2 - alto);
|
|
350
|
+
a.moveTo(w + 3, TH / 2 - h).lineTo(w + 3, TH / 2 - h - alto);
|
|
351
|
+
a.stroke({ color: inm ? 0xc79a4e : 0xcbb999, width: 0.9, alpha: inm ? 0.8 : 0.35 });
|
|
352
|
+
g.addChild(a);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// A crane: a genuinely big site, five open positions or more.
|
|
356
|
+
if (d.andamios >= 5) g.addChild(grua(alto + 26));
|
|
357
|
+
|
|
358
|
+
// A crack: CI is red.
|
|
359
|
+
if (d.grieta) {
|
|
360
|
+
g.addChild(
|
|
361
|
+
new Graphics()
|
|
362
|
+
.moveTo(0, TH / 2 - 2)
|
|
363
|
+
.lineTo(w * 0.4, TH / 2 - alto * 0.45)
|
|
364
|
+
.lineTo(w * 0.15, TH / 2 - alto * 0.72)
|
|
365
|
+
.stroke({ color: 0xe2604f, width: 1.3, alpha: 0.95 }),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Pallets: commits with no PR, waiting for somebody to lay them.
|
|
370
|
+
if (d.pisos === 0 && d.actividad30 > 0) g.addChild(ladrillos(col));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function grua(altura: number) {
|
|
374
|
+
const c = new Container();
|
|
375
|
+
const met = 0xb9a37a;
|
|
376
|
+
const g = new Graphics();
|
|
377
|
+
g.moveTo(-2, TH / 2).lineTo(-2, TH / 2 - altura);
|
|
378
|
+
g.moveTo(2, TH / 2).lineTo(2, TH / 2 - altura);
|
|
379
|
+
for (let y = 0; y < altura; y += 7) g.moveTo(-2, TH / 2 - y).lineTo(2, TH / 2 - y - 7);
|
|
380
|
+
g.moveTo(-16, TH / 2 - altura + 4).lineTo(26, TH / 2 - altura + 4); // pluma
|
|
381
|
+
g.moveTo(18, TH / 2 - altura + 4).lineTo(18, TH / 2 - altura + 16); // cable
|
|
382
|
+
g.stroke({ color: met, width: 1, alpha: 0.75 });
|
|
383
|
+
c.addChild(g);
|
|
384
|
+
c.addChild(new Graphics().rect(16, TH / 2 - altura + 16, 4, 3).fill({ color: met, alpha: 0.8 }));
|
|
385
|
+
return c;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function ladrillos(col: number) {
|
|
389
|
+
const g = new Graphics();
|
|
390
|
+
for (let i = 0; i < 4; i++) {
|
|
391
|
+
g.rect(-TW * 0.42 - (i % 2) * 5, TH * 0.5 + Math.floor(i / 2) * 3, 4, 2.4).fill({
|
|
392
|
+
color: tono(col, -0.2),
|
|
393
|
+
alpha: 0.75,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return g;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** A tree, so an empty plot reads as a plot and not as a hole. */
|
|
400
|
+
export function arbol() {
|
|
401
|
+
const c = new Container();
|
|
402
|
+
c.addChild(new Graphics().ellipse(0, TH * 0.5, 5, 2.2).fill({ color: 0x000000, alpha: 0.22 }));
|
|
403
|
+
c.addChild(new Graphics().rect(-0.9, TH * 0.5 - 6, 1.8, 6).fill({ color: 0x5d4b34 }));
|
|
404
|
+
c.addChild(
|
|
405
|
+
new Graphics()
|
|
406
|
+
.poly([0, TH * 0.5 - 19, -6, TH * 0.5 - 5, 6, TH * 0.5 - 5])
|
|
407
|
+
.fill({ color: 0x3f6b4a })
|
|
408
|
+
.poly([0, TH * 0.5 - 24, -4.5, TH * 0.5 - 12, 4.5, TH * 0.5 - 12])
|
|
409
|
+
.fill({ color: 0x4a7d55 }),
|
|
410
|
+
);
|
|
411
|
+
return c;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** The district floor: chequered isometric tiles, a fence and a banner. */
|
|
415
|
+
export function suelo(cols: number, filas: number, col: number, lab: boolean) {
|
|
416
|
+
const g = new Container();
|
|
417
|
+
|
|
418
|
+
// The ground, one plot per parcel, chequered so the grid reads.
|
|
419
|
+
const piso = new Graphics();
|
|
420
|
+
for (let x = -1; x < cols + 1; x++) {
|
|
421
|
+
for (let y = -1; y < filas + 1; y++) {
|
|
422
|
+
const p = iso(x * PASO, y * PASO);
|
|
423
|
+
const claro = (x + y) % 2 === 0;
|
|
424
|
+
piso
|
|
425
|
+
.poly([p.x, p.y, p.x + TW / 2, p.y + TH / 2, p.x, p.y + TH, p.x - TW / 2, p.y + TH / 2])
|
|
426
|
+
.fill({ color: col, alpha: claro ? 0.085 : 0.055 });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
g.addChild(piso);
|
|
430
|
+
g.addChild(calles(cols, filas));
|
|
431
|
+
g.addChild(farolas(cols, filas));
|
|
432
|
+
|
|
433
|
+
// Perimeter fence: dashed in the lab, which is an open site on purpose.
|
|
434
|
+
const k = PASO;
|
|
435
|
+
const a = iso(-1.5 * k, -1.5 * k),
|
|
436
|
+
b = iso((cols + 0.5) * k, -1.5 * k);
|
|
437
|
+
const c = iso((cols + 0.5) * k, (filas + 0.5) * k),
|
|
438
|
+
d = iso(-1.5 * k, (filas + 0.5) * k);
|
|
439
|
+
g.addChild(
|
|
440
|
+
new Graphics()
|
|
441
|
+
.poly([a.x, a.y + TH / 2, b.x, b.y + TH / 2, c.x, c.y + TH / 2, d.x, d.y + TH / 2])
|
|
442
|
+
.stroke({ color: col, width: 1.2, alpha: lab ? 0.55 : 0.35 }),
|
|
443
|
+
);
|
|
444
|
+
return g;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Streets: one avenue down the side of the district and one road between every
|
|
449
|
+
* row of plots.
|
|
450
|
+
*
|
|
451
|
+
* Taken from how software cities are laid out in the literature — EvoStreets
|
|
452
|
+
* puts the hierarchy in the streets — but here they carry no data on purpose.
|
|
453
|
+
* They are what turns a chequerboard into somewhere: without them the eye reads
|
|
454
|
+
* a grid of objects, with them it reads blocks, and blocks are how anybody has
|
|
455
|
+
* ever navigated a city.
|
|
456
|
+
*/
|
|
457
|
+
function calles(cols: number, filas: number) {
|
|
458
|
+
const g = new Graphics();
|
|
459
|
+
const ASFALTO = 0x1b2532,
|
|
460
|
+
LINEA = 0xc8b48a;
|
|
461
|
+
const ancho = 0.42; // half-width of a road, in tiles
|
|
462
|
+
|
|
463
|
+
/** A band between two points of the plot grid, drawn as an isometric quad. */
|
|
464
|
+
const banda = (x0: number, y0: number, x1: number, y1: number, w: number) => {
|
|
465
|
+
const p = [iso(x0 - w, y0 - w), iso(x1 + w, y1 - w), iso(x1 + w, y1 + w), iso(x0 - w, y0 + w)];
|
|
466
|
+
g.poly([
|
|
467
|
+
p[0].x,
|
|
468
|
+
p[0].y + TH / 2,
|
|
469
|
+
p[1].x,
|
|
470
|
+
p[1].y + TH / 2,
|
|
471
|
+
p[2].x,
|
|
472
|
+
p[2].y + TH / 2,
|
|
473
|
+
p[3].x,
|
|
474
|
+
p[3].y + TH / 2,
|
|
475
|
+
]);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// Roads between rows, and the avenue along the near edge.
|
|
479
|
+
for (let f = 0; f < filas; f++) {
|
|
480
|
+
const y = (f + 0.5) * PASO;
|
|
481
|
+
banda(-0.9 * PASO, y, (cols - 0.1) * PASO, y, ancho);
|
|
482
|
+
}
|
|
483
|
+
banda(-0.95 * PASO, -0.9 * PASO, -0.95 * PASO, (filas - 0.1) * PASO, ancho);
|
|
484
|
+
g.fill({ color: ASFALTO, alpha: 0.92 });
|
|
485
|
+
|
|
486
|
+
// The dashes down the middle of each road. Short, faint: at the zoom where the
|
|
487
|
+
// whole city fits they should read as texture, not as markings.
|
|
488
|
+
for (let f = 0; f < filas; f++) {
|
|
489
|
+
const y = (f + 0.5) * PASO;
|
|
490
|
+
for (let x = -0.7; x < cols - 0.2; x += 0.5) {
|
|
491
|
+
const a = iso(x * PASO, y),
|
|
492
|
+
b = iso((x + 0.22) * PASO, y);
|
|
493
|
+
g.moveTo(a.x, a.y + TH / 2).lineTo(b.x, b.y + TH / 2);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
g.stroke({ color: LINEA, width: 0.9, alpha: 0.3 });
|
|
497
|
+
return g;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Street lamps, and the pool of light under each one.
|
|
502
|
+
*
|
|
503
|
+
* The one thing that makes a night city read as inhabited rather than as a dark
|
|
504
|
+
* diagram. They carry no data — they are not lit by activity, that is what the
|
|
505
|
+
* windows are for — and there are few of them on purpose: a lamp on every plot
|
|
506
|
+
* would flatten the map into an even glow and take the contrast away from the
|
|
507
|
+
* houses that do have something to say.
|
|
508
|
+
*/
|
|
509
|
+
function farolas(cols: number, filas: number) {
|
|
510
|
+
const c = new Container();
|
|
511
|
+
const luz = new Graphics();
|
|
512
|
+
const poste = new Graphics();
|
|
513
|
+
for (let f = 0; f < filas; f++) {
|
|
514
|
+
for (let x = -0.5; x < cols; x += 2) {
|
|
515
|
+
const p = iso(x * PASO, (f + 0.5) * PASO);
|
|
516
|
+
const bx = p.x,
|
|
517
|
+
by = p.y + TH / 2;
|
|
518
|
+
luz.ellipse(bx, by, TW * 0.42, TH * 0.42).fill({ color: 0xffd98a, alpha: 0.07 });
|
|
519
|
+
luz.ellipse(bx, by, TW * 0.2, TH * 0.2).fill({ color: 0xffd98a, alpha: 0.06 });
|
|
520
|
+
poste
|
|
521
|
+
.moveTo(bx, by)
|
|
522
|
+
.lineTo(bx, by - 15)
|
|
523
|
+
.stroke({ color: 0x8d99a8, width: 0.9, alpha: 0.75 });
|
|
524
|
+
poste.circle(bx, by - 16, 1.6).fill({ color: 0xffd98a, alpha: 0.95 });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
luz.blendMode = 'add';
|
|
528
|
+
c.addChild(luz, poste);
|
|
529
|
+
return c;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* The ground the whole city stands on, and the avenues between its blocks.
|
|
534
|
+
*
|
|
535
|
+
* Drawn once, under everything, across the full extent of the layout. Without it
|
|
536
|
+
* the blocks float on nothing and the roads inside each one stop at its edge —
|
|
537
|
+
* which is what made this read as eight rugs on a floor rather than as a place.
|
|
538
|
+
*/
|
|
539
|
+
export function plano(
|
|
540
|
+
anchoCeldas: number,
|
|
541
|
+
altoCeldas: number,
|
|
542
|
+
bloques: { ox: number; oy: number; cols: number; filas: number }[],
|
|
543
|
+
) {
|
|
544
|
+
const c = new Container();
|
|
545
|
+
const suelo = new Graphics();
|
|
546
|
+
const w = anchoCeldas + 1,
|
|
547
|
+
h = altoCeldas + 1;
|
|
548
|
+
|
|
549
|
+
// The base plane, one flat quad in isometric.
|
|
550
|
+
const e = [
|
|
551
|
+
iso(-2 * PASO, -2 * PASO),
|
|
552
|
+
iso(w * PASO, -2 * PASO),
|
|
553
|
+
iso(w * PASO, h * PASO),
|
|
554
|
+
iso(-2 * PASO, h * PASO),
|
|
555
|
+
];
|
|
556
|
+
suelo
|
|
557
|
+
.poly([
|
|
558
|
+
e[0].x,
|
|
559
|
+
e[0].y + TH / 2,
|
|
560
|
+
e[1].x,
|
|
561
|
+
e[1].y + TH / 2,
|
|
562
|
+
e[2].x,
|
|
563
|
+
e[2].y + TH / 2,
|
|
564
|
+
e[3].x,
|
|
565
|
+
e[3].y + TH / 2,
|
|
566
|
+
])
|
|
567
|
+
.fill({ color: 0x0b1119, alpha: 0.92 });
|
|
568
|
+
|
|
569
|
+
// The avenues: the gaps the layout leaves between blocks, filled in as road.
|
|
570
|
+
const banda = (x0: number, y0: number, x1: number, y1: number, ancho: number) => {
|
|
571
|
+
const p = [
|
|
572
|
+
iso(x0 - ancho, y0 - ancho),
|
|
573
|
+
iso(x1 + ancho, y1 - ancho),
|
|
574
|
+
iso(x1 + ancho, y1 + ancho),
|
|
575
|
+
iso(x0 - ancho, y0 + ancho),
|
|
576
|
+
];
|
|
577
|
+
suelo.poly([
|
|
578
|
+
p[0].x,
|
|
579
|
+
p[0].y + TH / 2,
|
|
580
|
+
p[1].x,
|
|
581
|
+
p[1].y + TH / 2,
|
|
582
|
+
p[2].x,
|
|
583
|
+
p[2].y + TH / 2,
|
|
584
|
+
p[3].x,
|
|
585
|
+
p[3].y + TH / 2,
|
|
586
|
+
]);
|
|
587
|
+
};
|
|
588
|
+
const vistas = new Set<string>();
|
|
589
|
+
for (const b of bloques) {
|
|
590
|
+
const yAv = (b.oy + b.filas + 0.5) * PASO; // behind the block
|
|
591
|
+
const xAv = (b.ox + b.cols + 0.5) * PASO; // to its side
|
|
592
|
+
if (!vistas.has('y' + yAv)) {
|
|
593
|
+
vistas.add('y' + yAv);
|
|
594
|
+
banda(-2 * PASO, yAv, w * PASO, yAv, 0.6);
|
|
595
|
+
}
|
|
596
|
+
if (!vistas.has('x' + xAv)) {
|
|
597
|
+
vistas.add('x' + xAv);
|
|
598
|
+
banda(xAv, -2 * PASO, xAv, h * PASO, 0.6);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
suelo.fill({ color: 0x1b2532, alpha: 0.9 });
|
|
602
|
+
c.addChild(suelo);
|
|
603
|
+
return c;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** The district banner: a mast and cloth carrying the name. */
|
|
607
|
+
export function estandarte(nombre: string, dato: string, col: number) {
|
|
608
|
+
const c = new Container();
|
|
609
|
+
const g = new Graphics()
|
|
610
|
+
.moveTo(0, 0)
|
|
611
|
+
.lineTo(0, -46)
|
|
612
|
+
.stroke({ color: 0x8d99a8, width: 1.2, alpha: 0.6 });
|
|
613
|
+
c.addChild(g);
|
|
614
|
+
const txt = new Text({
|
|
615
|
+
text: nombre.toUpperCase(),
|
|
616
|
+
style: new TextStyle({
|
|
617
|
+
fontFamily: 'Chakra Petch, sans-serif',
|
|
618
|
+
fontSize: 12,
|
|
619
|
+
fontWeight: '700',
|
|
620
|
+
letterSpacing: 1.6,
|
|
621
|
+
fill: 0x131a24,
|
|
622
|
+
}),
|
|
623
|
+
});
|
|
624
|
+
txt.position.set(9, -44);
|
|
625
|
+
const tela = new Graphics()
|
|
626
|
+
.poly([2, -47, txt.width + 20, -47, txt.width + 14, -37, txt.width + 20, -27, 2, -27])
|
|
627
|
+
.fill({ color: col, alpha: 0.92 });
|
|
628
|
+
c.addChild(tela, txt);
|
|
629
|
+
const sub = new Text({
|
|
630
|
+
text: dato,
|
|
631
|
+
style: new TextStyle({ fontFamily: 'IBM Plex Mono, monospace', fontSize: 9, fill: 0x8d99a8 }),
|
|
632
|
+
});
|
|
633
|
+
sub.position.set(9, -23);
|
|
634
|
+
c.addChild(sub);
|
|
635
|
+
// The second line is only legible close up. Marked so the zoom can drop it,
|
|
636
|
+
// the same way it drops the trade under a person: a label too small to read is
|
|
637
|
+
// not information, it is noise on top of the map.
|
|
638
|
+
(c as any).__sub = sub;
|
|
639
|
+
return c;
|
|
640
|
+
}
|