c64-kb 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2082 -0
- package/LICENSE +29 -0
- package/README.md +559 -0
- package/VERSION +25 -0
- package/dist/cli/setup.js +66 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli-memorize-audit.js +97 -0
- package/dist/cli-memorize-audit.js.map +1 -0
- package/dist/cli.js +335 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +83 -0
- package/dist/config.js.map +1 -0
- package/dist/context.js +53 -0
- package/dist/context.js.map +1 -0
- package/dist/domain/budget-notes.js +104 -0
- package/dist/domain/budget-notes.js.map +1 -0
- package/dist/domain/budget.js +417 -0
- package/dist/domain/budget.js.map +1 -0
- package/dist/domain/game-design.js +77 -0
- package/dist/domain/game-design.js.map +1 -0
- package/dist/domain/timing.js +39 -0
- package/dist/domain/timing.js.map +1 -0
- package/dist/graph/apply.js +132 -0
- package/dist/graph/apply.js.map +1 -0
- package/dist/graph/claims.js +231 -0
- package/dist/graph/claims.js.map +1 -0
- package/dist/graph/extract/archetype.js +118 -0
- package/dist/graph/extract/archetype.js.map +1 -0
- package/dist/graph/extract/common.js +69 -0
- package/dist/graph/extract/common.js.map +1 -0
- package/dist/graph/extract/failure.js +37 -0
- package/dist/graph/extract/failure.js.map +1 -0
- package/dist/graph/extract/game-design.js +189 -0
- package/dist/graph/extract/game-design.js.map +1 -0
- package/dist/graph/extract/hardware.js +124 -0
- package/dist/graph/extract/hardware.js.map +1 -0
- package/dist/graph/extract/pitfall.js +62 -0
- package/dist/graph/extract/pitfall.js.map +1 -0
- package/dist/graph/extract/raster-band.js +56 -0
- package/dist/graph/extract/raster-band.js.map +1 -0
- package/dist/graph/extract/recipe.js +71 -0
- package/dist/graph/extract/recipe.js.map +1 -0
- package/dist/graph/extract/technique-entities.js +180 -0
- package/dist/graph/extract/technique-entities.js.map +1 -0
- package/dist/graph/extract/technique.js +179 -0
- package/dist/graph/extract/technique.js.map +1 -0
- package/dist/graph/extract/toolchain.js +67 -0
- package/dist/graph/extract/toolchain.js.map +1 -0
- package/dist/graph/extract/types.js +3 -0
- package/dist/graph/extract/types.js.map +1 -0
- package/dist/graph/extract/vocabulary.js +65 -0
- package/dist/graph/extract/vocabulary.js.map +1 -0
- package/dist/graph/extract.js +43 -0
- package/dist/graph/extract.js.map +1 -0
- package/dist/graph/kernal-clobbers.js +64 -0
- package/dist/graph/kernal-clobbers.js.map +1 -0
- package/dist/graph/machine-variants.js +208 -0
- package/dist/graph/machine-variants.js.map +1 -0
- package/dist/ingest/files.js +99 -0
- package/dist/ingest/files.js.map +1 -0
- package/dist/ingest/passes.js +104 -0
- package/dist/ingest/passes.js.map +1 -0
- package/dist/ingest/points.js +50 -0
- package/dist/ingest/points.js.map +1 -0
- package/dist/ingest/report.js +140 -0
- package/dist/ingest/report.js.map +1 -0
- package/dist/ingest/tally.js +81 -0
- package/dist/ingest/tally.js.map +1 -0
- package/dist/ingest/verified-on.js +43 -0
- package/dist/ingest/verified-on.js.map +1 -0
- package/dist/ingest.js +135 -0
- package/dist/ingest.js.map +1 -0
- package/dist/schemas/cost-basis.js +5 -0
- package/dist/schemas/cost-basis.js.map +1 -0
- package/dist/schemas/machine-variant.js +25 -0
- package/dist/schemas/machine-variant.js.map +1 -0
- package/dist/schemas/plan-budget.js +121 -0
- package/dist/schemas/plan-budget.js.map +1 -0
- package/dist/schemas/tool-outputs.js +526 -0
- package/dist/schemas/tool-outputs.js.map +1 -0
- package/dist/server/define-tool.js +52 -0
- package/dist/server/define-tool.js.map +1 -0
- package/dist/server/defined-only.js +9 -0
- package/dist/server/defined-only.js.map +1 -0
- package/dist/server/tools-briefings.js +70 -0
- package/dist/server/tools-briefings.js.map +1 -0
- package/dist/server/tools-lookup.js +180 -0
- package/dist/server/tools-lookup.js.map +1 -0
- package/dist/server/tools-maintenance.js +116 -0
- package/dist/server/tools-maintenance.js.map +1 -0
- package/dist/server/tools-pitfalls.js +89 -0
- package/dist/server/tools-pitfalls.js.map +1 -0
- package/dist/server/tools-recipes.js +296 -0
- package/dist/server/tools-recipes.js.map +1 -0
- package/dist/server/tools-runtime.js +48 -0
- package/dist/server/tools-runtime.js.map +1 -0
- package/dist/server/tools.js +43 -0
- package/dist/server/tools.js.map +1 -0
- package/dist/server.js +95 -0
- package/dist/server.js.map +1 -0
- package/dist/services/analytics/schema.js +78 -0
- package/dist/services/analytics/schema.js.map +1 -0
- package/dist/services/analytics/statements.js +99 -0
- package/dist/services/analytics/statements.js.map +1 -0
- package/dist/services/analytics.js +195 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/bm25.js +158 -0
- package/dist/services/bm25.js.map +1 -0
- package/dist/services/chunker.js +81 -0
- package/dist/services/chunker.js.map +1 -0
- package/dist/services/embeddings.js +58 -0
- package/dist/services/embeddings.js.map +1 -0
- package/dist/services/falkor/base.js +85 -0
- package/dist/services/falkor/base.js.map +1 -0
- package/dist/services/falkor/links.js +361 -0
- package/dist/services/falkor/links.js.map +1 -0
- package/dist/services/falkor/nodes.js +183 -0
- package/dist/services/falkor/nodes.js.map +1 -0
- package/dist/services/falkor/params.js +45 -0
- package/dist/services/falkor/params.js.map +1 -0
- package/dist/services/falkor/schema.js +77 -0
- package/dist/services/falkor/schema.js.map +1 -0
- package/dist/services/falkor.js +100 -0
- package/dist/services/falkor.js.map +1 -0
- package/dist/services/qdrant.js +238 -0
- package/dist/services/qdrant.js.map +1 -0
- package/dist/services/versions.js +73 -0
- package/dist/services/versions.js.map +1 -0
- package/dist/services/vice-bin.js +51 -0
- package/dist/services/vice-bin.js.map +1 -0
- package/dist/tools/briefings/archetype.js +174 -0
- package/dist/tools/briefings/archetype.js.map +1 -0
- package/dist/tools/briefings/budget.js +177 -0
- package/dist/tools/briefings/budget.js.map +1 -0
- package/dist/tools/briefings/build-order.js +135 -0
- package/dist/tools/briefings/build-order.js.map +1 -0
- package/dist/tools/briefings/build.js +185 -0
- package/dist/tools/briefings/build.js.map +1 -0
- package/dist/tools/briefings/discovery.js +290 -0
- package/dist/tools/briefings/discovery.js.map +1 -0
- package/dist/tools/briefings/plan-pitfalls.js +69 -0
- package/dist/tools/briefings/plan-pitfalls.js.map +1 -0
- package/dist/tools/briefings/render.js +151 -0
- package/dist/tools/briefings/render.js.map +1 -0
- package/dist/tools/briefings/rows.js +41 -0
- package/dist/tools/briefings/rows.js.map +1 -0
- package/dist/tools/briefings/toolchain.js +63 -0
- package/dist/tools/briefings/toolchain.js.map +1 -0
- package/dist/tools/briefings/why-proposed.js +69 -0
- package/dist/tools/briefings/why-proposed.js.map +1 -0
- package/dist/tools/briefings.js +44 -0
- package/dist/tools/briefings.js.map +1 -0
- package/dist/tools/feedback.js +97 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/hydrate.js +75 -0
- package/dist/tools/hydrate.js.map +1 -0
- package/dist/tools/intelligence.js +67 -0
- package/dist/tools/intelligence.js.map +1 -0
- package/dist/tools/lint/asm-decimal.js +86 -0
- package/dist/tools/lint/asm-decimal.js.map +1 -0
- package/dist/tools/lint/asm-rules.js +221 -0
- package/dist/tools/lint/asm-rules.js.map +1 -0
- package/dist/tools/lint/asm-shared.js +3 -0
- package/dist/tools/lint/asm-shared.js.map +1 -0
- package/dist/tools/lint/c-rules.js +215 -0
- package/dist/tools/lint/c-rules.js.map +1 -0
- package/dist/tools/lint/text.js +122 -0
- package/dist/tools/lint/text.js.map +1 -0
- package/dist/tools/lint/types.js +40 -0
- package/dist/tools/lint/types.js.map +1 -0
- package/dist/tools/lint.js +76 -0
- package/dist/tools/lint.js.map +1 -0
- package/dist/tools/memorization-mcp.js +111 -0
- package/dist/tools/memorization-mcp.js.map +1 -0
- package/dist/tools/pitfalls/bm25.js +53 -0
- package/dist/tools/pitfalls/bm25.js.map +1 -0
- package/dist/tools/pitfalls/format.js +58 -0
- package/dist/tools/pitfalls/format.js.map +1 -0
- package/dist/tools/pitfalls/graph.js +124 -0
- package/dist/tools/pitfalls/graph.js.map +1 -0
- package/dist/tools/pitfalls.js +134 -0
- package/dist/tools/pitfalls.js.map +1 -0
- package/dist/tools/prompts.js +81 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/query/compatibility/closure.js +62 -0
- package/dist/tools/query/compatibility/closure.js.map +1 -0
- package/dist/tools/query/compatibility/facts.js +28 -0
- package/dist/tools/query/compatibility/facts.js.map +1 -0
- package/dist/tools/query/compatibility/fetch.js +164 -0
- package/dist/tools/query/compatibility/fetch.js.map +1 -0
- package/dist/tools/query/compatibility/hard-rules.js +159 -0
- package/dist/tools/query/compatibility/hard-rules.js.map +1 -0
- package/dist/tools/query/compatibility/index.js +27 -0
- package/dist/tools/query/compatibility/index.js.map +1 -0
- package/dist/tools/query/compatibility/kernal-zp-rule.js +90 -0
- package/dist/tools/query/compatibility/kernal-zp-rule.js.map +1 -0
- package/dist/tools/query/compatibility/render.js +106 -0
- package/dist/tools/query/compatibility/render.js.map +1 -0
- package/dist/tools/query/compatibility/rules.js +337 -0
- package/dist/tools/query/compatibility/rules.js.map +1 -0
- package/dist/tools/query/compatibility/unit-rules.js +253 -0
- package/dist/tools/query/compatibility/unit-rules.js.map +1 -0
- package/dist/tools/query/game-design.js +83 -0
- package/dist/tools/query/game-design.js.map +1 -0
- package/dist/tools/query/lookups.js +220 -0
- package/dist/tools/query/lookups.js.map +1 -0
- package/dist/tools/query/plan-budget.js +266 -0
- package/dist/tools/query/plan-budget.js.map +1 -0
- package/dist/tools/query/recipes.js +214 -0
- package/dist/tools/query/recipes.js.map +1 -0
- package/dist/tools/query/retrieval.js +128 -0
- package/dist/tools/query/retrieval.js.map +1 -0
- package/dist/tools/query/shared.js +121 -0
- package/dist/tools/query/shared.js.map +1 -0
- package/dist/tools/query/techniques.js +331 -0
- package/dist/tools/query/techniques.js.map +1 -0
- package/dist/tools/query/timing.js +106 -0
- package/dist/tools/query/timing.js.map +1 -0
- package/dist/tools/query/types.js +11 -0
- package/dist/tools/query/types.js.map +1 -0
- package/dist/tools/query.js +21 -0
- package/dist/tools/query.js.map +1 -0
- package/dist/tools/resources.js +186 -0
- package/dist/tools/resources.js.map +1 -0
- package/dist/tools/run-game.js +249 -0
- package/dist/tools/run-game.js.map +1 -0
- package/dist/tools/selfimprovement.js +367 -0
- package/dist/tools/selfimprovement.js.map +1 -0
- package/docker-compose.yml +35 -0
- package/docs/ARCHITECTURE.md +164 -0
- package/docs/CONVENTIONS-archetypes.md +158 -0
- package/docs/CONVENTIONS-failures.md +46 -0
- package/docs/CONVENTIONS-game-designs.md +75 -0
- package/docs/CONVENTIONS-hardware-reference.md +184 -0
- package/docs/CONVENTIONS-pitfalls.md +91 -0
- package/docs/CONVENTIONS-recipes.md +106 -0
- package/docs/CONVENTIONS-techniques.md +309 -0
- package/docs/CONVENTIONS-toolchain-reference.md +89 -0
- package/docs/ONTOLOGY.md +655 -0
- package/docs/art/art-production-reference.md +157 -0
- package/docs/art/asset-pipelines.md +586 -0
- package/docs/c64-failure-patterns.md +693 -0
- package/docs/demo-design/demo-composition.md +349 -0
- package/docs/demo-design/demo-design-philosophy.md +320 -0
- package/docs/demo-design/intro-cracktro-patterns.md +435 -0
- package/docs/figures/ctm-spd-embed-probe.png +0 -0
- package/docs/figures/dot-flag-mid-5000000.png +0 -0
- package/docs/figures/dot-flag-no-erase-12000000.png +0 -0
- package/docs/figures/edges.json +1 -0
- package/docs/figures/fig6-architecture.png +0 -0
- package/docs/figures/fig7-ontology.png +0 -0
- package/docs/figures/screen-dissolve-mid-frame25.png +0 -0
- package/docs/figures/screen-dissolve-wipe-frame25.png +0 -0
- package/docs/figures/spindle-two-parts-part1.png +0 -0
- package/docs/figures/spindle-two-parts-part2.png +0 -0
- package/docs/figures/starters/action-puzzle.png +0 -0
- package/docs/figures/starters/adventure.png +0 -0
- package/docs/figures/starters/beat-em-up.png +0 -0
- package/docs/figures/starters/demo.png +0 -0
- package/docs/figures/starters/platformer.png +0 -0
- package/docs/figures/starters/shmup-vertical.png +0 -0
- package/docs/figures/tape-kernal-master-ntsc.png +0 -0
- package/docs/figures/tape-kernal-master-pal.png +0 -0
- package/docs/figures/tape-turbo-loader-ntsc.png +0 -0
- package/docs/figures/tape-turbo-loader-pal.png +0 -0
- package/docs/figures/tape-turbo-master-ntsc.png +0 -0
- package/docs/figures/tape-turbo-master-pal.png +0 -0
- package/docs/figures/tech-tech-pad2-12000000.png +0 -0
- package/docs/figures/tech-tech-pad4-12000000.png +0 -0
- package/docs/figures/tech-tech-seven-12000000.png +0 -0
- package/docs/figures/twister-mid-5000000.png +0 -0
- package/docs/figures/twister-straight-12000000.png +0 -0
- package/docs/figures/vector-balls-nosort-ntsc.png +0 -0
- package/docs/figures/vector-balls-nosort.png +0 -0
- package/docs/formats/c64-file-formats.md +1212 -0
- package/docs/formats/iec-disk-reference.md +754 -0
- package/docs/game-design/c64-game-archetypes.md +273 -0
- package/docs/game-design/designs/falling-blocks.md +54 -0
- package/docs/game-design/designs/platformer-scaffold.md +48 -0
- package/docs/game-design/designs/simple-shmup.md +27 -0
- package/docs/game-design/enemy-behaviour-and-difficulty.md +349 -0
- package/docs/game-design/game-design-patterns.md +1189 -0
- package/docs/game-design/game-structure.md +354 -0
- package/docs/game-design/production-planning.md +349 -0
- package/docs/game-design/reference-game-sources.md +90 -0
- package/docs/hardware/6502-illegal-opcodes.md +1353 -0
- package/docs/hardware/6510-cpu-reference.md +2112 -0
- package/docs/hardware/c64-memory-map.md +3184 -0
- package/docs/hardware/c64-registers-reference.md +996 -0
- package/docs/hardware/cia-reference.md +1281 -0
- package/docs/hardware/kernal-routines-reference.md +1836 -0
- package/docs/hardware/pal-ntsc-reference.md +813 -0
- package/docs/hardware/sid-reference.md +1137 -0
- package/docs/hardware/vic-ii-reference.md +1714 -0
- package/docs/music/music-production-reference.md +403 -0
- package/docs/pitfalls/banking.md +1067 -0
- package/docs/pitfalls/cia.md +1147 -0
- package/docs/pitfalls/cpu.md +874 -0
- package/docs/pitfalls/input.md +282 -0
- package/docs/pitfalls/kernal-and-io.md +1615 -0
- package/docs/pitfalls/loader.md +1191 -0
- package/docs/pitfalls/logic.md +94 -0
- package/docs/pitfalls/maths.md +187 -0
- package/docs/pitfalls/raster-and-badline.md +721 -0
- package/docs/pitfalls/region-timing.md +660 -0
- package/docs/pitfalls/scroll.md +207 -0
- package/docs/pitfalls/sid.md +738 -0
- package/docs/pitfalls/sprite.md +979 -0
- package/docs/pitfalls/text-mode-render.md +1172 -0
- package/docs/recipes/cc65/cartridge-8k.md +398 -0
- package/docs/recipes/cc65/hello-world-conio.md +76 -0
- package/docs/recipes/cc65/memory-layout.md +312 -0
- package/docs/recipes/cc65/screenshots/README.md +14 -0
- package/docs/recipes/cc65/screenshots/cartridge-8k-ntsc.png +0 -0
- package/docs/recipes/cc65/screenshots/cartridge-8k.png +0 -0
- package/docs/recipes/cc65/screenshots/hello-world-conio.png +0 -0
- package/docs/recipes/cc65/screenshots/memory-layout-ntsc.png +0 -0
- package/docs/recipes/cc65/screenshots/memory-layout.png +0 -0
- package/docs/recipes/kickassembler/basic-float-calls.md +578 -0
- package/docs/recipes/kickassembler/basic-wedge.md +398 -0
- package/docs/recipes/kickassembler/big-font-scroller.md +620 -0
- package/docs/recipes/kickassembler/charset-copy-rom-to-ram.md +623 -0
- package/docs/recipes/kickassembler/cia-revision-detect.md +366 -0
- package/docs/recipes/kickassembler/colour-cycling.md +343 -0
- package/docs/recipes/kickassembler/colour-fade.md +303 -0
- package/docs/recipes/kickassembler/compare-16bit-signed.md +1007 -0
- package/docs/recipes/kickassembler/cracktro-template.md +526 -0
- package/docs/recipes/kickassembler/crt-banked.md +276 -0
- package/docs/recipes/kickassembler/dos-error-codes.md +466 -0
- package/docs/recipes/kickassembler/dot-flag.md +576 -0
- package/docs/recipes/kickassembler/drive-job-queue.md +667 -0
- package/docs/recipes/kickassembler/drive-via-probe.md +690 -0
- package/docs/recipes/kickassembler/dycp-scroller.md +568 -0
- package/docs/recipes/kickassembler/dypp-sprite-scroller.md +681 -0
- package/docs/recipes/kickassembler/dysp.md +670 -0
- package/docs/recipes/kickassembler/easyflash-save.md +800 -0
- package/docs/recipes/kickassembler/eight-way-scroll.md +1484 -0
- package/docs/recipes/kickassembler/file-io-roundtrip.md +546 -0
- package/docs/recipes/kickassembler/fire-effect.md +420 -0
- package/docs/recipes/kickassembler/fld.md +414 -0
- package/docs/recipes/kickassembler/fli-image.md +336 -0
- package/docs/recipes/kickassembler/four-player-read.md +672 -0
- package/docs/recipes/kickassembler/headless-verify.md +228 -0
- package/docs/recipes/kickassembler/hello-world.md +98 -0
- package/docs/recipes/kickassembler/high-score-insert.md +660 -0
- package/docs/recipes/kickassembler/hires-plot-line.md +723 -0
- package/docs/recipes/kickassembler/irq-chain.md +316 -0
- package/docs/recipes/kickassembler/irq-owns-port.md +668 -0
- package/docs/recipes/kickassembler/isometric-room.md +1145 -0
- package/docs/recipes/kickassembler/light-pen-read.md +926 -0
- package/docs/recipes/kickassembler/logic-rate-decoupling.md +670 -0
- package/docs/recipes/kickassembler/mci-interlace.md +543 -0
- package/docs/recipes/kickassembler/memory-layout.md +277 -0
- package/docs/recipes/kickassembler/mouse-1351-read.md +801 -0
- package/docs/recipes/kickassembler/nmi-timer-tick.md +562 -0
- package/docs/recipes/kickassembler/own-keyscan.md +881 -0
- package/docs/recipes/kickassembler/paddle-read.md +751 -0
- package/docs/recipes/kickassembler/palette-cells.md +219 -0
- package/docs/recipes/kickassembler/pseudo-3d-road.md +930 -0
- package/docs/recipes/kickassembler/pwm-digi.md +463 -0
- package/docs/recipes/kickassembler/raster-bars.md +264 -0
- package/docs/recipes/kickassembler/reu-dma.md +1019 -0
- package/docs/recipes/kickassembler/runtime-relocation.md +743 -0
- package/docs/recipes/kickassembler/screen-dissolve.md +545 -0
- package/docs/recipes/kickassembler/screen-wipe.md +408 -0
- package/docs/recipes/kickassembler/screenshots/README.md +72 -0
- package/docs/recipes/kickassembler/screenshots/basic-float-calls-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/basic-float-calls.png +0 -0
- package/docs/recipes/kickassembler/screenshots/basic-wedge-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/basic-wedge.png +0 -0
- package/docs/recipes/kickassembler/screenshots/big-font-scroller-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/big-font-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/charset-copy-rom-to-ram-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/charset-copy-rom-to-ram.png +0 -0
- package/docs/recipes/kickassembler/screenshots/cia-revision-detect-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/cia-revision-detect.png +0 -0
- package/docs/recipes/kickassembler/screenshots/colour-cycling-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/colour-cycling.png +0 -0
- package/docs/recipes/kickassembler/screenshots/colour-fade-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/colour-fade.png +0 -0
- package/docs/recipes/kickassembler/screenshots/compare-16bit-signed-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/compare-16bit-signed.png +0 -0
- package/docs/recipes/kickassembler/screenshots/cracktro-template.png +0 -0
- package/docs/recipes/kickassembler/screenshots/crt-banked-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/crt-banked.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dos-error-codes-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dos-error-codes.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dot-flag-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dot-flag.png +0 -0
- package/docs/recipes/kickassembler/screenshots/drive-job-queue-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/drive-job-queue.png +0 -0
- package/docs/recipes/kickassembler/screenshots/drive-via-probe-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/drive-via-probe.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dycp-scroller-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dycp-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dypp-sprite-scroller-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dypp-sprite-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dysp-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dysp.png +0 -0
- package/docs/recipes/kickassembler/screenshots/easyflash-save-ntsc-run2.png +0 -0
- package/docs/recipes/kickassembler/screenshots/easyflash-save-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/easyflash-save-run2.png +0 -0
- package/docs/recipes/kickassembler/screenshots/easyflash-save.png +0 -0
- package/docs/recipes/kickassembler/screenshots/eight-way-scroll-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/eight-way-scroll.png +0 -0
- package/docs/recipes/kickassembler/screenshots/file-io-roundtrip-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/file-io-roundtrip.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fire-effect-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fire-effect.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fld-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fld.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fli-image.png +0 -0
- package/docs/recipes/kickassembler/screenshots/four-player-read-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/four-player-read.png +0 -0
- package/docs/recipes/kickassembler/screenshots/headless-verify-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/headless-verify.png +0 -0
- package/docs/recipes/kickassembler/screenshots/hello-world.png +0 -0
- package/docs/recipes/kickassembler/screenshots/high-score-insert-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/high-score-insert.png +0 -0
- package/docs/recipes/kickassembler/screenshots/hires-plot-line-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/hires-plot-line.png +0 -0
- package/docs/recipes/kickassembler/screenshots/irq-chain-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/irq-chain.png +0 -0
- package/docs/recipes/kickassembler/screenshots/irq-owns-port-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/irq-owns-port.png +0 -0
- package/docs/recipes/kickassembler/screenshots/isometric-room-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/isometric-room.png +0 -0
- package/docs/recipes/kickassembler/screenshots/light-pen-read-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/light-pen-read.png +0 -0
- package/docs/recipes/kickassembler/screenshots/logic-rate-decoupling-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/logic-rate-decoupling.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mci-interlace-average.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mci-interlace-frame-b.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mci-interlace-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mci-interlace.png +0 -0
- package/docs/recipes/kickassembler/screenshots/memory-layout-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/memory-layout.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mouse-1351-read-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mouse-1351-read.png +0 -0
- package/docs/recipes/kickassembler/screenshots/nmi-timer-tick-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/nmi-timer-tick.png +0 -0
- package/docs/recipes/kickassembler/screenshots/own-keyscan-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/own-keyscan.png +0 -0
- package/docs/recipes/kickassembler/screenshots/paddle-read-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/paddle-read.png +0 -0
- package/docs/recipes/kickassembler/screenshots/palette-cells-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/palette-cells.png +0 -0
- package/docs/recipes/kickassembler/screenshots/pseudo-3d-road-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/pseudo-3d-road.png +0 -0
- package/docs/recipes/kickassembler/screenshots/pwm-digi-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/pwm-digi.png +0 -0
- package/docs/recipes/kickassembler/screenshots/raster-bars.png +0 -0
- package/docs/recipes/kickassembler/screenshots/reu-dma-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/reu-dma.png +0 -0
- package/docs/recipes/kickassembler/screenshots/runtime-relocation-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/runtime-relocation.png +0 -0
- package/docs/recipes/kickassembler/screenshots/screen-dissolve-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/screen-dissolve.png +0 -0
- package/docs/recipes/kickassembler/screenshots/screen-wipe-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/screen-wipe.png +0 -0
- package/docs/recipes/kickassembler/screenshots/scroll-panel-split-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/scroll-panel-split.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sfx-in-player-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sfx-in-player.png +0 -0
- package/docs/recipes/kickassembler/screenshots/shadebobs-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/shadebobs.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sideborder-open.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sine-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sine-table-runtime-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sine-table-runtime.png +0 -0
- package/docs/recipes/kickassembler/screenshots/software-sprite-preshifted-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/software-sprite-preshifted.png +0 -0
- package/docs/recipes/kickassembler/screenshots/speedcode-generator-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/speedcode-generator.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-border-scroller-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-border-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-cache-flip-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-cache-flip.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-multiplex-24.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-multiplex-game-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-multiplex-game.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-priority-classes-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-priority-classes.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-sine-chain-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-sine-chain.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-stretcher-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-stretcher.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprites-only-screen-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprites-only-screen.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sqrt-atan2-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sqrt-atan2.png +0 -0
- package/docs/recipes/kickassembler/screenshots/stable-raster-irq-control.png +0 -0
- package/docs/recipes/kickassembler/screenshots/stable-raster-irq.png +0 -0
- package/docs/recipes/kickassembler/screenshots/tech-tech-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/tech-tech.png +0 -0
- package/docs/recipes/kickassembler/screenshots/tod-alarm-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/tod-alarm.png +0 -0
- package/docs/recipes/kickassembler/screenshots/topbottom-border-open.png +0 -0
- package/docs/recipes/kickassembler/screenshots/twister-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/twister.png +0 -0
- package/docs/recipes/kickassembler/screenshots/unit-test-driver-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/unit-test-driver.png +0 -0
- package/docs/recipes/kickassembler/screenshots/vector-balls-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/vector-balls.png +0 -0
- package/docs/recipes/kickassembler/screenshots/wireframe-ships-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/wireframe-ships.png +0 -0
- package/docs/recipes/kickassembler/scroll-panel-split.md +381 -0
- package/docs/recipes/kickassembler/sfx-in-player.md +907 -0
- package/docs/recipes/kickassembler/shadebobs.md +522 -0
- package/docs/recipes/kickassembler/sideborder-open.md +338 -0
- package/docs/recipes/kickassembler/sine-scroller.md +321 -0
- package/docs/recipes/kickassembler/sine-table-runtime.md +815 -0
- package/docs/recipes/kickassembler/software-sprite-preshifted.md +725 -0
- package/docs/recipes/kickassembler/speedcode-generator.md +606 -0
- package/docs/recipes/kickassembler/sprite-border-scroller.md +696 -0
- package/docs/recipes/kickassembler/sprite-cache-flip.md +1071 -0
- package/docs/recipes/kickassembler/sprite-multiplex-24.md +326 -0
- package/docs/recipes/kickassembler/sprite-multiplex-game.md +986 -0
- package/docs/recipes/kickassembler/sprite-priority-classes.md +463 -0
- package/docs/recipes/kickassembler/sprite-sine-chain.md +338 -0
- package/docs/recipes/kickassembler/sprite-stretcher.md +501 -0
- package/docs/recipes/kickassembler/sprites-only-screen.md +550 -0
- package/docs/recipes/kickassembler/sqrt-atan2.md +1014 -0
- package/docs/recipes/kickassembler/stable-raster-irq.md +360 -0
- package/docs/recipes/kickassembler/tape-turbo-loader.md +875 -0
- package/docs/recipes/kickassembler/tech-tech.md +679 -0
- package/docs/recipes/kickassembler/tod-alarm.md +754 -0
- package/docs/recipes/kickassembler/topbottom-border-open.md +407 -0
- package/docs/recipes/kickassembler/twister.md +518 -0
- package/docs/recipes/kickassembler/unit-test-driver.md +314 -0
- package/docs/recipes/kickassembler/vector-balls.md +545 -0
- package/docs/recipes/kickassembler/wireframe-ships.md +2324 -0
- package/docs/recipes/oscar64/actor-activation-window.md +807 -0
- package/docs/recipes/oscar64/adventure-engine.md +1094 -0
- package/docs/recipes/oscar64/attract-replay.md +465 -0
- package/docs/recipes/oscar64/beat-em-up-lanes.md +563 -0
- package/docs/recipes/oscar64/bfs-distance-map.md +723 -0
- package/docs/recipes/oscar64/bitmap-koala-viewer.md +355 -0
- package/docs/recipes/oscar64/car-contact.md +950 -0
- package/docs/recipes/oscar64/cave-scan.md +609 -0
- package/docs/recipes/oscar64/char-bullets.md +477 -0
- package/docs/recipes/oscar64/charset-animation.md +383 -0
- package/docs/recipes/oscar64/charset-parallax.md +401 -0
- package/docs/recipes/oscar64/destructible-terrain.md +1099 -0
- package/docs/recipes/oscar64/difficulty-tables.md +654 -0
- package/docs/recipes/oscar64/dig-and-guards.md +975 -0
- package/docs/recipes/oscar64/directory-reader.md +535 -0
- package/docs/recipes/oscar64/divide-check.md +581 -0
- package/docs/recipes/oscar64/double-buffer-nomirror.md +205 -0
- package/docs/recipes/oscar64/double-buffer.md +255 -0
- package/docs/recipes/oscar64/falling-blocks.md +874 -0
- package/docs/recipes/oscar64/fixed-point-jump-velocity.md +289 -0
- package/docs/recipes/oscar64/fixed-point-jump.md +210 -0
- package/docs/recipes/oscar64/flip-screen-rooms.md +793 -0
- package/docs/recipes/oscar64/frame-sync-loop-overrun.md +214 -0
- package/docs/recipes/oscar64/frame-sync-loop.md +254 -0
- package/docs/recipes/oscar64/game-tree-search.md +743 -0
- package/docs/recipes/oscar64/ghost-targeting.md +867 -0
- package/docs/recipes/oscar64/headless-verify.md +345 -0
- package/docs/recipes/oscar64/hello-world.md +83 -0
- package/docs/recipes/oscar64/high-score-persist.md +772 -0
- package/docs/recipes/oscar64/iffl-kernal-skip.md +421 -0
- package/docs/recipes/oscar64/joystick-input.md +358 -0
- package/docs/recipes/oscar64/lane-pursuit.md +1028 -0
- package/docs/recipes/oscar64/level-rle-decoder.md +620 -0
- package/docs/recipes/oscar64/lfsr-random-seed2.md +338 -0
- package/docs/recipes/oscar64/lfsr-random.md +403 -0
- package/docs/recipes/oscar64/load-asset-runtime.md +492 -0
- package/docs/recipes/oscar64/memory-layout.md +232 -0
- package/docs/recipes/oscar64/mixed-fighters.md +781 -0
- package/docs/recipes/oscar64/multi-sprite-object.md +533 -0
- package/docs/recipes/oscar64/nav-area-pathfinding.md +881 -0
- package/docs/recipes/oscar64/object-pool.md +572 -0
- package/docs/recipes/oscar64/pal-ntsc-detect.md +257 -0
- package/docs/recipes/oscar64/password-state.md +532 -0
- package/docs/recipes/oscar64/per-frame-hitbox.md +651 -0
- package/docs/recipes/oscar64/petscii-screen-codes.md +428 -0
- package/docs/recipes/oscar64/platformer-scaffold.md +1467 -0
- package/docs/recipes/oscar64/print-number.md +367 -0
- package/docs/recipes/oscar64/raster-bars.md +239 -0
- package/docs/recipes/oscar64/raster-profile-bars.md +404 -0
- package/docs/recipes/oscar64/rel-side-sectors.md +391 -0
- package/docs/recipes/oscar64/relative-file-records.md +378 -0
- package/docs/recipes/oscar64/save-load-seq-file.md +365 -0
- package/docs/recipes/oscar64/screenshots/README.md +99 -0
- package/docs/recipes/oscar64/screenshots/actor-activation-window-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/actor-activation-window.png +0 -0
- package/docs/recipes/oscar64/screenshots/adventure-engine-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/adventure-engine.png +0 -0
- package/docs/recipes/oscar64/screenshots/attract-replay-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/attract-replay.png +0 -0
- package/docs/recipes/oscar64/screenshots/beat-em-up-lanes-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/beat-em-up-lanes.png +0 -0
- package/docs/recipes/oscar64/screenshots/bfs-distance-map-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/bfs-distance-map.png +0 -0
- package/docs/recipes/oscar64/screenshots/bitmap-koala-viewer.png +0 -0
- package/docs/recipes/oscar64/screenshots/car-contact-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/car-contact.png +0 -0
- package/docs/recipes/oscar64/screenshots/cave-scan-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/cave-scan.png +0 -0
- package/docs/recipes/oscar64/screenshots/char-bullets-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/char-bullets.png +0 -0
- package/docs/recipes/oscar64/screenshots/charset-animation-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/charset-animation.png +0 -0
- package/docs/recipes/oscar64/screenshots/charset-parallax-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/charset-parallax.png +0 -0
- package/docs/recipes/oscar64/screenshots/destructible-terrain-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/destructible-terrain.png +0 -0
- package/docs/recipes/oscar64/screenshots/difficulty-tables-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/difficulty-tables.png +0 -0
- package/docs/recipes/oscar64/screenshots/dig-and-guards-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/dig-and-guards.png +0 -0
- package/docs/recipes/oscar64/screenshots/directory-reader-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/directory-reader.png +0 -0
- package/docs/recipes/oscar64/screenshots/divide-check-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/divide-check.png +0 -0
- package/docs/recipes/oscar64/screenshots/double-buffer-nomirror.png +0 -0
- package/docs/recipes/oscar64/screenshots/double-buffer-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/double-buffer.png +0 -0
- package/docs/recipes/oscar64/screenshots/falling-blocks-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/falling-blocks.png +0 -0
- package/docs/recipes/oscar64/screenshots/fixed-point-jump-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/fixed-point-jump-velocity-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/fixed-point-jump-velocity.png +0 -0
- package/docs/recipes/oscar64/screenshots/fixed-point-jump.png +0 -0
- package/docs/recipes/oscar64/screenshots/flip-screen-rooms-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/flip-screen-rooms.png +0 -0
- package/docs/recipes/oscar64/screenshots/frame-sync-loop-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/frame-sync-loop-overrun.png +0 -0
- package/docs/recipes/oscar64/screenshots/frame-sync-loop.png +0 -0
- package/docs/recipes/oscar64/screenshots/game-tree-search-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/game-tree-search.png +0 -0
- package/docs/recipes/oscar64/screenshots/ghost-targeting-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/ghost-targeting.png +0 -0
- package/docs/recipes/oscar64/screenshots/headless-verify-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/headless-verify.png +0 -0
- package/docs/recipes/oscar64/screenshots/hello-world.png +0 -0
- package/docs/recipes/oscar64/screenshots/high-score-persist-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/high-score-persist.png +0 -0
- package/docs/recipes/oscar64/screenshots/iffl-kernal-skip-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/iffl-kernal-skip.png +0 -0
- package/docs/recipes/oscar64/screenshots/joystick-input-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/joystick-input.png +0 -0
- package/docs/recipes/oscar64/screenshots/lane-pursuit-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/lane-pursuit.png +0 -0
- package/docs/recipes/oscar64/screenshots/level-rle-decoder-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/level-rle-decoder.png +0 -0
- package/docs/recipes/oscar64/screenshots/lfsr-random-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/lfsr-random-seed2.png +0 -0
- package/docs/recipes/oscar64/screenshots/lfsr-random.png +0 -0
- package/docs/recipes/oscar64/screenshots/load-asset-runtime-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/load-asset-runtime.png +0 -0
- package/docs/recipes/oscar64/screenshots/memory-layout-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/memory-layout.png +0 -0
- package/docs/recipes/oscar64/screenshots/mixed-fighters-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/mixed-fighters.png +0 -0
- package/docs/recipes/oscar64/screenshots/multi-sprite-object-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/multi-sprite-object.png +0 -0
- package/docs/recipes/oscar64/screenshots/nav-area-pathfinding-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/nav-area-pathfinding.png +0 -0
- package/docs/recipes/oscar64/screenshots/object-pool-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/object-pool.png +0 -0
- package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-oldntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-pal.png +0 -0
- package/docs/recipes/oscar64/screenshots/password-state-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/password-state.png +0 -0
- package/docs/recipes/oscar64/screenshots/per-frame-hitbox-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/per-frame-hitbox.png +0 -0
- package/docs/recipes/oscar64/screenshots/petscii-screen-codes-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/petscii-screen-codes.png +0 -0
- package/docs/recipes/oscar64/screenshots/platformer-scaffold-d64-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/platformer-scaffold-d64.png +0 -0
- package/docs/recipes/oscar64/screenshots/platformer-scaffold-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/platformer-scaffold.png +0 -0
- package/docs/recipes/oscar64/screenshots/print-number-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/print-number.png +0 -0
- package/docs/recipes/oscar64/screenshots/raster-bars.png +0 -0
- package/docs/recipes/oscar64/screenshots/raster-profile-bars-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/raster-profile-bars.png +0 -0
- package/docs/recipes/oscar64/screenshots/rel-side-sectors-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/rel-side-sectors.png +0 -0
- package/docs/recipes/oscar64/screenshots/relative-file-records-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/relative-file-records.png +0 -0
- package/docs/recipes/oscar64/screenshots/save-load-seq-file-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/save-load-seq-file.png +0 -0
- package/docs/recipes/oscar64/screenshots/seeded-level-fill-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/seeded-level-fill.png +0 -0
- package/docs/recipes/oscar64/screenshots/sfx-engine-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/sfx-engine.png +0 -0
- package/docs/recipes/oscar64/screenshots/sid-music-player.png +0 -0
- package/docs/recipes/oscar64/screenshots/simple-shmup.png +0 -0
- package/docs/recipes/oscar64/screenshots/slope-collision-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/slope-collision.png +0 -0
- package/docs/recipes/oscar64/screenshots/soft-scroll-h.png +0 -0
- package/docs/recipes/oscar64/screenshots/sprite-animation-table-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/sprite-animation-table.png +0 -0
- package/docs/recipes/oscar64/screenshots/sprite-multiplex-8.png +0 -0
- package/docs/recipes/oscar64/screenshots/stable-raster-irq.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-input-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-input.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield-gameover.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield-notrail-control.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield-ungated-control.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-window-menu-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-window-menu.png +0 -0
- package/docs/recipes/oscar64/screenshots/tile-grid-collision-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/tile-grid-collision.png +0 -0
- package/docs/recipes/oscar64/screenshots/tile-map-render-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/tile-map-render.png +0 -0
- package/docs/recipes/oscar64/screenshots/two-player-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/two-player.png +0 -0
- package/docs/recipes/oscar64/screenshots/two-word-parser-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/two-word-parser.png +0 -0
- package/docs/recipes/oscar64/screenshots/vehicle-control-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/vehicle-control.png +0 -0
- package/docs/recipes/oscar64/screenshots/wave-director-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/wave-director.png +0 -0
- package/docs/recipes/oscar64/seeded-level-fill.md +364 -0
- package/docs/recipes/oscar64/sfx-engine.md +514 -0
- package/docs/recipes/oscar64/sid-music-player.md +441 -0
- package/docs/recipes/oscar64/simple-shmup.md +1000 -0
- package/docs/recipes/oscar64/slope-collision.md +610 -0
- package/docs/recipes/oscar64/soft-scroll-h.md +261 -0
- package/docs/recipes/oscar64/sprite-animation-table.md +671 -0
- package/docs/recipes/oscar64/sprite-multiplex-8.md +314 -0
- package/docs/recipes/oscar64/stable-raster-irq.md +236 -0
- package/docs/recipes/oscar64/text-input.md +274 -0
- package/docs/recipes/oscar64/text-overlay-playfield.md +617 -0
- package/docs/recipes/oscar64/text-window-menu.md +551 -0
- package/docs/recipes/oscar64/tile-grid-collision.md +563 -0
- package/docs/recipes/oscar64/tile-map-render.md +395 -0
- package/docs/recipes/oscar64/two-player.md +528 -0
- package/docs/recipes/oscar64/two-word-parser.md +510 -0
- package/docs/recipes/oscar64/vehicle-control.md +771 -0
- package/docs/recipes/oscar64/wave-director.md +787 -0
- package/docs/recipes/runs.json +1606 -0
- package/docs/runtime/sim6502-reference.md +466 -0
- package/docs/runtime/vice-mcp-reference.md +351 -0
- package/docs/runtime/vice-reference.md +1393 -0
- package/docs/techniques/bitmap-modes.md +685 -0
- package/docs/techniques/cpu-cycle-tricks.md +1570 -0
- package/docs/techniques/effects-vector-3d.md +1431 -0
- package/docs/techniques/file-io.md +1427 -0
- package/docs/techniques/input.md +1072 -0
- package/docs/techniques/loaders-packers.md +768 -0
- package/docs/techniques/logic.md +3091 -0
- package/docs/techniques/maths.md +1524 -0
- package/docs/techniques/memory-banking.md +1686 -0
- package/docs/techniques/music-sid.md +1144 -0
- package/docs/techniques/raster.md +1543 -0
- package/docs/techniques/scroll.md +1292 -0
- package/docs/techniques/sprite.md +2566 -0
- package/docs/techniques/text-mode-render.md +717 -0
- package/docs/techniques/text.md +1095 -0
- package/docs/techniques/transitions.md +405 -0
- package/docs/toolchains/cartconv-reference.md +262 -0
- package/docs/toolchains/cc1541-reference.md +316 -0
- package/docs/toolchains/cc65-reference.md +667 -0
- package/docs/toolchains/error-sources/cc65/README.md +1 -0
- package/docs/toolchains/error-sources/cc65/missing-setcpu.s +4 -0
- package/docs/toolchains/error-sources/cc65/range-error.s +4 -0
- package/docs/toolchains/error-sources/cc65/segment-overflow.c +6 -0
- package/docs/toolchains/error-sources/cc65/unresolved-external.c +6 -0
- package/docs/toolchains/error-sources/kickassembler/README.md +1 -0
- package/docs/toolchains/error-sources/kickassembler/branch-out-of-range.asm +4 -0
- package/docs/toolchains/error-sources/kickassembler/failed-assert.asm +3 -0
- package/docs/toolchains/error-sources/kickassembler/flat-lib.asm +4 -0
- package/docs/toolchains/error-sources/kickassembler/import-source-no-namespace.asm +5 -0
- package/docs/toolchains/error-sources/kickassembler/macro-without-parentheses.asm +6 -0
- package/docs/toolchains/error-sources/kickassembler/memory-block-overlap.asm +4 -0
- package/docs/toolchains/error-sources/kickassembler/symbol-clash-lib.asm +5 -0
- package/docs/toolchains/error-sources/kickassembler/symbol-clash-main.asm +6 -0
- package/docs/toolchains/error-sources/kickassembler/unknown-symbol.asm +4 -0
- package/docs/toolchains/error-sources/oscar64/README.md +1 -0
- package/docs/toolchains/error-sources/oscar64/asm-addressing-mode.c +7 -0
- package/docs/toolchains/error-sources/oscar64/region-overflow.c +6 -0
- package/docs/toolchains/error-sources/oscar64/undefined-extern-var.c +5 -0
- package/docs/toolchains/error-sources/oscar64/undefined-extern.c +6 -0
- package/docs/toolchains/error-sources/oscar64/unknown-vic-field.c +6 -0
- package/docs/toolchains/error-sources/oscar64/void-main.c +3 -0
- package/docs/toolchains/kickassembler-reference.md +1020 -0
- package/docs/toolchains/memory-layout-planning.md +362 -0
- package/docs/toolchains/oscar64-headers-reference.md +583 -0
- package/docs/toolchains/oscar64-reference.md +892 -0
- package/docs/toolchains/petcat-reference.md +290 -0
- package/docs/toolchains/png2prg.md +188 -0
- package/docs/toolchains/release-disk.md +314 -0
- package/docs/toolchains/sidreloc.md +236 -0
- package/docs/toolchains/spindle-reference.md +487 -0
- package/docs/toolchains/tape-mastering.md +592 -0
- package/docs/toolchains/unit-testing-6502.md +281 -0
- package/docs/workflow/agent-harness.md +478 -0
- package/package.json +105 -0
- package/templates/_harness/CLAUDE.md.template +91 -0
- package/templates/_harness/PLAN.md.template +72 -0
- package/templates/_harness/check.py +442 -0
- package/templates/_harness/claude/settings.json +17 -0
- package/templates/_harness/gen-asm-header.py +96 -0
- package/templates/_harness/harness.mk +273 -0
- package/templates/_harness/hooks/plan-gate.py +185 -0
- package/templates/_harness/meter/frame_meter.asm +311 -0
- package/templates/_harness/meter/frame_meter.c +155 -0
- package/templates/_harness/meter/frame_meter.h +80 -0
- package/templates/_harness/zp-used.py +104 -0
- package/templates/action-puzzle/.claude/settings.json +17 -0
- package/templates/action-puzzle/.mcp.json +10 -0
- package/templates/action-puzzle/CLAUDE.md +120 -0
- package/templates/action-puzzle/Makefile +125 -0
- package/templates/action-puzzle/PLAN.md +388 -0
- package/templates/action-puzzle/README.md +240 -0
- package/templates/action-puzzle/expect.json +25 -0
- package/templates/action-puzzle/src/cave.c +280 -0
- package/templates/action-puzzle/src/cave.h +80 -0
- package/templates/action-puzzle/src/gen_autopilot.h +20 -0
- package/templates/action-puzzle/src/gen_caves.h +59 -0
- package/templates/action-puzzle/src/gen_notes.h +16 -0
- package/templates/action-puzzle/src/hiscore.c +152 -0
- package/templates/action-puzzle/src/hiscore.h +41 -0
- package/templates/action-puzzle/src/level.c +64 -0
- package/templates/action-puzzle/src/level.h +24 -0
- package/templates/action-puzzle/src/main.c +599 -0
- package/templates/action-puzzle/src/render.c +239 -0
- package/templates/action-puzzle/src/render.h +31 -0
- package/templates/action-puzzle/src/sound.c +158 -0
- package/templates/action-puzzle/src/sound.h +22 -0
- package/templates/action-puzzle/tools/disk_check.py +77 -0
- package/templates/action-puzzle/tools/drive.py +106 -0
- package/templates/action-puzzle/tools/gen.py +454 -0
- package/templates/action-puzzle/tools/model_check.py +49 -0
- package/templates/action-puzzle/tools/verdict_codes.py +24 -0
- package/templates/adventure/.claude/settings.json +17 -0
- package/templates/adventure/.mcp.json +10 -0
- package/templates/adventure/CLAUDE.md +118 -0
- package/templates/adventure/Makefile +113 -0
- package/templates/adventure/PLAN.md +396 -0
- package/templates/adventure/README.md +277 -0
- package/templates/adventure/expect.json +25 -0
- package/templates/adventure/src/engine.c +543 -0
- package/templates/adventure/src/engine.h +48 -0
- package/templates/adventure/src/gen_script.h +141 -0
- package/templates/adventure/src/gen_world.h +511 -0
- package/templates/adventure/src/main.c +439 -0
- package/templates/adventure/src/picture.c +83 -0
- package/templates/adventure/src/picture.h +16 -0
- package/templates/adventure/src/save.c +159 -0
- package/templates/adventure/src/save.h +25 -0
- package/templates/adventure/src/sound.c +71 -0
- package/templates/adventure/src/sound.h +16 -0
- package/templates/adventure/src/text.c +349 -0
- package/templates/adventure/src/text.h +40 -0
- package/templates/adventure/tools/disk_check.py +128 -0
- package/templates/adventure/tools/drive.py +119 -0
- package/templates/adventure/tools/gen.py +1033 -0
- package/templates/adventure/tools/world.py +356 -0
- package/templates/beat-em-up/.claude/settings.json +17 -0
- package/templates/beat-em-up/.mcp.json +10 -0
- package/templates/beat-em-up/CLAUDE.md +136 -0
- package/templates/beat-em-up/Makefile +96 -0
- package/templates/beat-em-up/PLAN.md +502 -0
- package/templates/beat-em-up/README.md +315 -0
- package/templates/beat-em-up/expect-gameover.json +8 -0
- package/templates/beat-em-up/expect.json +29 -0
- package/templates/beat-em-up/src/anim.c +70 -0
- package/templates/beat-em-up/src/art.c +344 -0
- package/templates/beat-em-up/src/autopilot.h +154 -0
- package/templates/beat-em-up/src/brute.c +273 -0
- package/templates/beat-em-up/src/enemy.c +281 -0
- package/templates/beat-em-up/src/engine.asm +512 -0
- package/templates/beat-em-up/src/fighter.c +384 -0
- package/templates/beat-em-up/src/game.h +269 -0
- package/templates/beat-em-up/src/hud.c +135 -0
- package/templates/beat-em-up/src/main.c +341 -0
- package/templates/beat-em-up/src/sound.c +44 -0
- package/templates/beat-em-up/src/street.c +93 -0
- package/templates/beat-em-up/src/verdict.h +523 -0
- package/templates/beat-em-up/src/view.c +362 -0
- package/templates/beat-em-up/tools/drive.py +105 -0
- package/templates/beat-em-up/tools/flickercheck.py +273 -0
- package/templates/demo/.claude/settings.json +17 -0
- package/templates/demo/.mcp.json +10 -0
- package/templates/demo/CLAUDE.md +129 -0
- package/templates/demo/Makefile +66 -0
- package/templates/demo/PLAN.md +401 -0
- package/templates/demo/README.md +322 -0
- package/templates/demo/expect.json +945 -0
- package/templates/demo/src/config.asm +80 -0
- package/templates/demo/src/framework.asm +390 -0
- package/templates/demo/src/main.asm +139 -0
- package/templates/demo/src/music.asm +197 -0
- package/templates/demo/src/part_main.asm +279 -0
- package/templates/demo/src/part_title.asm +79 -0
- package/templates/demo/src/parts.asm +43 -0
- package/templates/demo/src/tables.asm +73 -0
- package/templates/demo/src/verdict.asm +179 -0
- package/templates/demo/tools/audio.py +80 -0
- package/templates/demo/tools/gen_expect.py +167 -0
- package/templates/demo/tools/probe.py +95 -0
- package/templates/hello/.claude/settings.json +17 -0
- package/templates/hello/.mcp.json +10 -0
- package/templates/hello/CLAUDE.md +100 -0
- package/templates/hello/Makefile +38 -0
- package/templates/hello/PLAN.md +200 -0
- package/templates/hello/expect.json +20 -0
- package/templates/hello/src/main.c +161 -0
- package/templates/hello/src/sprite.asm +27 -0
- package/templates/hello-kick/.claude/settings.json +17 -0
- package/templates/hello-kick/.mcp.json +10 -0
- package/templates/hello-kick/CLAUDE.md +100 -0
- package/templates/hello-kick/Makefile +17 -0
- package/templates/hello-kick/PLAN.md +199 -0
- package/templates/hello-kick/expect.json +20 -0
- package/templates/hello-kick/src/main.asm +233 -0
- package/templates/platformer/.claude/settings.json +17 -0
- package/templates/platformer/.mcp.json +10 -0
- package/templates/platformer/CLAUDE.md +125 -0
- package/templates/platformer/Makefile +86 -0
- package/templates/platformer/PLAN.md +360 -0
- package/templates/platformer/README.md +277 -0
- package/templates/platformer/expect.json +32 -0
- package/templates/platformer/src/actors.c +289 -0
- package/templates/platformer/src/anim.c +51 -0
- package/templates/platformer/src/art.c +212 -0
- package/templates/platformer/src/autopilot.h +30 -0
- package/templates/platformer/src/engine.asm +253 -0
- package/templates/platformer/src/game.h +248 -0
- package/templates/platformer/src/hud.c +90 -0
- package/templates/platformer/src/level.c +184 -0
- package/templates/platformer/src/main.c +306 -0
- package/templates/platformer/src/player.c +268 -0
- package/templates/platformer/src/sound.c +42 -0
- package/templates/platformer/src/verdict.h +206 -0
- package/templates/platformer/src/view.c +331 -0
- package/templates/platformer/stage-expect.json +6 -0
- package/templates/platformer/tools/drive.py +112 -0
- package/templates/platformer/tools/tearcheck.py +229 -0
- package/templates/shmup-vertical/.claude/settings.json +17 -0
- package/templates/shmup-vertical/.mcp.json +10 -0
- package/templates/shmup-vertical/CLAUDE.md +137 -0
- package/templates/shmup-vertical/Makefile +131 -0
- package/templates/shmup-vertical/PLAN.md +569 -0
- package/templates/shmup-vertical/README.md +189 -0
- package/templates/shmup-vertical/expect.json +38 -0
- package/templates/shmup-vertical/src/bullets.c +158 -0
- package/templates/shmup-vertical/src/bullets.h +35 -0
- package/templates/shmup-vertical/src/display.c +239 -0
- package/templates/shmup-vertical/src/display.h +40 -0
- package/templates/shmup-vertical/src/game.h +85 -0
- package/templates/shmup-vertical/src/glyph.asm +358 -0
- package/templates/shmup-vertical/src/hiscore.c +141 -0
- package/templates/shmup-vertical/src/hiscore.h +16 -0
- package/templates/shmup-vertical/src/hit.asm +180 -0
- package/templates/shmup-vertical/src/hitbox.c +59 -0
- package/templates/shmup-vertical/src/hitbox.h +25 -0
- package/templates/shmup-vertical/src/kernel.asm +330 -0
- package/templates/shmup-vertical/src/level.c +174 -0
- package/templates/shmup-vertical/src/level.h +27 -0
- package/templates/shmup-vertical/src/main.c +824 -0
- package/templates/shmup-vertical/src/mux.asm +321 -0
- package/templates/shmup-vertical/src/sound.asm +255 -0
- package/templates/shmup-vertical/src/step.asm +169 -0
- package/templates/shmup-vertical/src/waves.c +195 -0
- package/templates/shmup-vertical/src/waves.h +33 -0
- package/templates/shmup-vertical/stage-expect.json +6 -0
- package/templates/shmup-vertical/tools/drive.py +152 -0
- package/templates/shmup-vertical/tools/joytest.py +159 -0
- package/templates/shmup-vertical/tools/meter.py +15 -0
- package/templates/shmup-vertical/tools/phases.py +73 -0
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: input
|
|
3
|
+
chip: CIA
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- doc-type: technique-reference -->
|
|
7
|
+
|
|
8
|
+
# Input Techniques
|
|
9
|
+
|
|
10
|
+
Reading the joystick ports, the paddles and the keyboard matrix directly
|
|
11
|
+
from CIA1 and the SID, without the KERNAL's once-per-jiffy scanner. The
|
|
12
|
+
techniques share one fact: CIA1 port A (`$DC00`) is control port 2 and the keyboard's
|
|
13
|
+
column drive, CIA1 port B (`$DC01`) is control port 1 and the keyboard's
|
|
14
|
+
row sense, and every switch on either port reads as a 0 bit when closed.
|
|
15
|
+
The wiring is in `hardware/cia-reference.md`; the ways it bites are in
|
|
16
|
+
`pitfalls/input.md`. Every number below that came from an instrument says
|
|
17
|
+
so; the rest is marked as arithmetic or as not measured here.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## joystick_edge_detect — Joystick press events by previous-frame XOR
|
|
22
|
+
|
|
23
|
+
**Complexity:** low
|
|
24
|
+
**Region:** both
|
|
25
|
+
**Uses registers:** DC00, DC01
|
|
26
|
+
**Claims:** cia1_port_a (reads), cia1_port_b (reads)
|
|
27
|
+
**Claims basis:** derived-listing
|
|
28
|
+
|
|
29
|
+
### Why
|
|
30
|
+
|
|
31
|
+
A port byte is a level: bit 4 low means the fire button is down right
|
|
32
|
+
now. A game usually wants an event: the frame the button went down, so
|
|
33
|
+
one press fires one shot, or the frame it came up. Reading the level
|
|
34
|
+
every frame and acting on it gives one shot per frame for as long as the
|
|
35
|
+
button is held.
|
|
36
|
+
|
|
37
|
+
### How
|
|
38
|
+
|
|
39
|
+
Keep last frame's port byte. Once per frame read the port again and
|
|
40
|
+
compare the two:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
pressed = ~cur & $1F ; lines that are low now
|
|
44
|
+
new = pressed & prev ; low now, high last frame
|
|
45
|
+
held = pressed & ~prev & $1F ; low now, low last frame
|
|
46
|
+
released = cur & ~prev & $1F ; high now, low last frame
|
|
47
|
+
prev = cur
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`cur ^ prev` is the set of lines that changed; masking that with `prev`
|
|
51
|
+
picks the ones that changed from high to low, which is the same set as
|
|
52
|
+
`new` above. Bit 0 is up, 1 down, 2 left, 3 right, 4 fire, on both ports
|
|
53
|
+
(`hardware/cia-reference.md`, and Oscar64's `joystick.c` tests the same
|
|
54
|
+
five bits). Bits 5 to 7 are keyboard lines and paddle plumbing; mask them
|
|
55
|
+
off or a held key turns into a phantom event.
|
|
56
|
+
|
|
57
|
+
Port 2 is `$DC00`, port 1 is `$DC01`. The same code serves both; only the
|
|
58
|
+
address differs. The first `prev` should be `$FF` (nothing pressed), so a
|
|
59
|
+
button already down when the program starts counts as a new press on
|
|
60
|
+
frame 0 rather than being missed.
|
|
61
|
+
|
|
62
|
+
Oscar64's library path is `joy_poll(n)` from `<c64/joystick.h>`, which
|
|
63
|
+
reads `$DC00 + n` (so `joy_poll(0)` is control port 2) and leaves
|
|
64
|
+
`joyx[n]`, `joyy[n]` as signed -1, 0 or 1 and `joyb[n]` as a bool (read
|
|
65
|
+
from the header and `joystick.c`). It reports levels, not edges; keep the
|
|
66
|
+
previous `joyb[n]` yourself if you use it. The bare-register path is one
|
|
67
|
+
`LDA $DC00` per frame and the four lines above.
|
|
68
|
+
|
|
69
|
+
### Why it works
|
|
70
|
+
|
|
71
|
+
The five switch lines have pull-ups inside the CIA; a closed switch grounds
|
|
72
|
+
its line and the port read returns 0 in that bit. Nothing latches: the byte
|
|
73
|
+
is the state of the switches at the cycle of the read. The comparison with
|
|
74
|
+
the previous frame is the only memory in the system, which is why the
|
|
75
|
+
program has to hold it.
|
|
76
|
+
|
|
77
|
+
Timing the read to the frame matters. The read is instantaneous; the
|
|
78
|
+
comparison assumes one read per frame. A read on a raster line the game
|
|
79
|
+
picks (the recipe waits for line 250) gives a steady 50 or 60 samples a
|
|
80
|
+
second on PAL or NTSC. If the KERNAL IRQ is still running, a main-loop read
|
|
81
|
+
of `$DC00` sees `$7F` at rest, not `$FF`: the KERNAL leaves column 7
|
|
82
|
+
driven low between scans (measured in `pitfalls/input.md`). With the
|
|
83
|
+
mask applied it makes no difference to the five joystick bits.
|
|
84
|
+
|
|
85
|
+
### Variations
|
|
86
|
+
|
|
87
|
+
**Released events.** `released` above is free; use it for a charge-and-let-go
|
|
88
|
+
shot or to end a jump early.
|
|
89
|
+
|
|
90
|
+
**Both ports in one pass.** Read `$DC00` and `$DC01` back to back into two
|
|
91
|
+
bytes and run the four lines twice. For port 1 write `$FF` to `$DC00`
|
|
92
|
+
first so no keyboard column is selected; otherwise a key held in the
|
|
93
|
+
selected column reads as a port 1 direction (see `keyboard_matrix_scan`).
|
|
94
|
+
|
|
95
|
+
**Diagonal cleanup.** Up and down cannot both be low on a working stick,
|
|
96
|
+
but a cheap one can bounce through it. Oscar64's `joy_poll` resolves the
|
|
97
|
+
conflict by testing up before down and left before right; do the same or
|
|
98
|
+
mask the pair to zero.
|
|
99
|
+
|
|
100
|
+
### Exhaustive check
|
|
101
|
+
|
|
102
|
+
The recipe below runs the four lines over every (prev, cur) pair, all
|
|
103
|
+
65,536 of them, and folds `new | held << 5 | released << 10` into a 16-bit
|
|
104
|
+
checksum. The 6502 shows `1800 PASS`; the same fold in Python gives `1800`
|
|
105
|
+
(measured in VICE x64sc 3.10, rung 1).
|
|
106
|
+
|
|
107
|
+
### Recipes
|
|
108
|
+
|
|
109
|
+
- `recipes/oscar64/joystick-input.md`
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## joystick_autorepeat — Delayed auto-repeat from per-direction age counters
|
|
114
|
+
|
|
115
|
+
**Complexity:** low
|
|
116
|
+
**Region:** both
|
|
117
|
+
**Uses registers:** DC00, DC01
|
|
118
|
+
**Requires:** joystick_edge_detect
|
|
119
|
+
**Claims:** cia1_port_a (reads), cia1_port_b (reads)
|
|
120
|
+
**Claims basis:** derived-listing
|
|
121
|
+
|
|
122
|
+
### Why
|
|
123
|
+
|
|
124
|
+
A falling-block or menu game wants a held direction to move once at
|
|
125
|
+
once, pause, then step at a steady rate. Acting on the level moves every
|
|
126
|
+
frame, which is too fast to steer; acting only on the press event needs a
|
|
127
|
+
tap per cell, which is too slow. The KERNAL keyboard queue has its own
|
|
128
|
+
repeat but no joystick equivalent and no way to tune the delay per
|
|
129
|
+
direction, which is why `game-design/c64-game-archetypes.md` says
|
|
130
|
+
action-puzzle games keep their own age counters.
|
|
131
|
+
|
|
132
|
+
### How
|
|
133
|
+
|
|
134
|
+
One byte per direction counts frames held. Each frame:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
if line is high: age = 0, no fire
|
|
138
|
+
else: age = age + 1
|
|
139
|
+
if age == DELAY + RATE: age = DELAY
|
|
140
|
+
fire if age == 1 or age == DELAY
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`DELAY` is the number of frames before the first repeat and `RATE` the
|
|
144
|
+
frames between repeats after that. Resetting `age` to `DELAY` instead of
|
|
145
|
+
letting it climb keeps the counter in a byte and avoids a modulo. The
|
|
146
|
+
recipe uses `DELAY` 20 and `RATE` 4: on PAL that is a first repeat after
|
|
147
|
+
0.4 s and then 12.5 steps a second, on NTSC 0.33 s and 15 a second
|
|
148
|
+
(arithmetic from 50 and 60 frames a second, rung 3). A 100-frame hold
|
|
149
|
+
fires 22 times: once at frame 1, once at frame 20, then every four frames
|
|
150
|
+
(arithmetic, and the same number from the Python model).
|
|
151
|
+
|
|
152
|
+
The first-frame fire is the press event from `joystick_edge_detect`
|
|
153
|
+
expressed through the counter: `age == 1` is true only on the frame after
|
|
154
|
+
the line went low. Games that already consume press events elsewhere can
|
|
155
|
+
drop that term and fire only on `age == DELAY`.
|
|
156
|
+
|
|
157
|
+
Frames, not jiffies. The counters advance once per frame the game draws,
|
|
158
|
+
so the feel tracks the region: the same `DELAY` is a fifth slower on PAL.
|
|
159
|
+
A region-aware program scales the two constants after
|
|
160
|
+
`pal_ntsc_detection` (`techniques/raster.md`) or accepts the difference.
|
|
161
|
+
|
|
162
|
+
### Why it works
|
|
163
|
+
|
|
164
|
+
The CIA gives no press history; the age counter is the history. Because it
|
|
165
|
+
is per direction, a player rolling from left to down keeps a fresh counter
|
|
166
|
+
for down while left's counter clears, so the first step in the new
|
|
167
|
+
direction is immediate. A single shared counter would inherit the old
|
|
168
|
+
direction's age and skip the initial delay.
|
|
169
|
+
|
|
170
|
+
### Variations
|
|
171
|
+
|
|
172
|
+
**Count-down timer.** Load a timer with `DELAY` on the press, decrement
|
|
173
|
+
while held, fire and reload with `RATE` when it hits zero. Same behaviour,
|
|
174
|
+
one compare fewer per frame, but the count of frames held is lost.
|
|
175
|
+
|
|
176
|
+
**Accelerating repeat.** Shrink `RATE` each time it fires, with a floor, for
|
|
177
|
+
a cursor that speeds up while held.
|
|
178
|
+
|
|
179
|
+
**Soft drop.** For a falling piece, hold down usually bypasses the delay
|
|
180
|
+
and moves every `RATE` frames from the first; use `DELAY` = 1 for that
|
|
181
|
+
direction only.
|
|
182
|
+
|
|
183
|
+
### Exhaustive check
|
|
184
|
+
|
|
185
|
+
The recipe runs the step over every (age, pressed) pair, 256 by 2, and
|
|
186
|
+
folds `age' | fire << 8` into the same 16-bit checksum. The 6502 shows
|
|
187
|
+
`D1CC PASS`; Python gives `D1CC` (measured in VICE x64sc 3.10, rung 1).
|
|
188
|
+
|
|
189
|
+
### Recipes
|
|
190
|
+
|
|
191
|
+
- `recipes/oscar64/joystick-input.md`
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## keyboard_matrix_scan — Read the keyboard matrix from the main loop, without the KERNAL
|
|
196
|
+
|
|
197
|
+
**Complexity:** medium
|
|
198
|
+
**Region:** both
|
|
199
|
+
**Uses registers:** DC00, DC01, DC02, DC03
|
|
200
|
+
**Claims:** cia1_port_a (owns), cia1_port_b (reads)
|
|
201
|
+
**Claims basis:** derived-listing
|
|
202
|
+
|
|
203
|
+
### Why
|
|
204
|
+
|
|
205
|
+
`GETIN` hands out one PETSCII character per call from a ten-byte queue.
|
|
206
|
+
It cannot say whether a key is still down, cannot see two keys at once
|
|
207
|
+
outside the shift and control modifiers, and runs on the jiffy IRQ's
|
|
208
|
+
schedule. A game that steers with keys needs the matrix itself: which of
|
|
209
|
+
the 64 switches is closed this frame.
|
|
210
|
+
|
|
211
|
+
### How
|
|
212
|
+
|
|
213
|
+
The keyboard is 8 columns by 8 rows. Columns hang on port A, rows on port
|
|
214
|
+
B. Set the direction registers once, then walk one low bit across the
|
|
215
|
+
columns and read the rows after each write:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
$DC02 = $FF ; port A all output (column drive)
|
|
219
|
+
$DC03 = $00 ; port B all input (row sense)
|
|
220
|
+
col = $FE
|
|
221
|
+
for i in 0..7:
|
|
222
|
+
$DC00 = col ; drive column i low, the other seven high
|
|
223
|
+
rows[i] = $DC01 ; bit r is 0 if key (column i, row r) is down
|
|
224
|
+
col = col << 1 | 1
|
|
225
|
+
$DC00 = $FF ; leave no column selected
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Key (column c, row r) is down when bit r of `rows[c]` is 0. The layout
|
|
229
|
+
table is in `hardware/cia-reference.md`; SPACE is column 7, row 4, so
|
|
230
|
+
`rows[7] & $10` is 0 while SPACE is held. Keep last frame's eight bytes
|
|
231
|
+
and the XOR trick from `joystick_edge_detect` gives press and release
|
|
232
|
+
events per key; an age counter per key gives auto-repeat.
|
|
233
|
+
|
|
234
|
+
The Oscar64 library does exactly this in `keyb_poll()` from
|
|
235
|
+
`<c64/keyboard.h>`: `keyb_matrix[8]` holds the eight row bytes as read,
|
|
236
|
+
`key_pressed(KSCAN_x)` tests bit `code & 7` of `keyb_matrix[code >> 3]`,
|
|
237
|
+
so the scan code is `column * 8 + row` and `KSCAN_SPACE` is 60. It also
|
|
238
|
+
keeps the previous scan and reports the newest press in `keyb_key` with
|
|
239
|
+
`KSCAN_QUAL_DOWN` (`$80`) set. All from the header and `keyboard.c`,
|
|
240
|
+
build 2026-05-19. The library's scan first tests `$DC01` with every
|
|
241
|
+
column driven low and returns early if it reads `$FF`, which is the
|
|
242
|
+
KERNAL's own no-key shortcut.
|
|
243
|
+
|
|
244
|
+
### Why it works
|
|
245
|
+
|
|
246
|
+
There are no diodes in the matrix: a closed switch is a wire from its
|
|
247
|
+
column line to its row line. With one column pulled low and the rows
|
|
248
|
+
floating high through their pull-ups, only switches in that column can pull
|
|
249
|
+
a row down. Eight passes cover the 64 switches. Leaving all columns high
|
|
250
|
+
afterwards, or all low, is a choice; the KERNAL leaves `$7F` (column 7
|
|
251
|
+
low), which is why the STOP key can be tested with a single read of
|
|
252
|
+
`$DC01` bit 7 (`hardware/c64-memory-map.md`).
|
|
253
|
+
|
|
254
|
+
**Ghosting.** Because a switch is just a wire, three closed keys at three
|
|
255
|
+
corners of a rectangle in the matrix connect the fourth corner's column to
|
|
256
|
+
its row through the other three, and the scan reads four keys. Two keys
|
|
257
|
+
in the same row or the same column never ghost; three keys sharing no
|
|
258
|
+
row or column never ghost. From the wiring (`hardware/cia-reference.md`),
|
|
259
|
+
not measured here: a headless VICE run cannot hold three keys.
|
|
260
|
+
|
|
261
|
+
**Why a main-loop scan and a joystick in port 1 interfere.** Port 1's five
|
|
262
|
+
switches sit on the row lines PB0 to PB4. A held direction grounds its
|
|
263
|
+
row regardless of which column is driven, so the scan reads that row as
|
|
264
|
+
pressed in all eight columns: up (PB0) looks like DEL, 3, 5, 7, 9, +,
|
|
265
|
+
pound and 1 held together. In the other direction, while the scan has
|
|
266
|
+
column c driven low, any key in column c pulls its row down, and a read of
|
|
267
|
+
`$DC01` taken as joystick 1 at that moment sees a direction. The KERNAL's
|
|
268
|
+
version of this, with column 7 left selected, is measured in
|
|
269
|
+
`pitfalls/input.md`: 1, left-arrow, CTRL, 2 and SPACE read as up, down,
|
|
270
|
+
left, right and fire on port 1. The fix is ordering: read port 1 only
|
|
271
|
+
with `$DC00` = `$FF`, and read the matrix only when the game does not also
|
|
272
|
+
expect port 1 input, or accept that a port 1 stick is a set of keys. Port
|
|
273
|
+
2 sits on the column lines PA0 to PA4; a held port 2 direction grounds a
|
|
274
|
+
column the scan did not select, so keys in that column show up in every
|
|
275
|
+
pass (from the wiring, not measured here). Games that use port 2 and the
|
|
276
|
+
keyboard together therefore see keyboard corruption only while the stick
|
|
277
|
+
is held, and only for keys in five of the eight columns.
|
|
278
|
+
|
|
279
|
+
**Why the KERNAL IRQ must be out of the way.** SCNKEY writes `$DC00` on
|
|
280
|
+
its own schedule inside the jiffy IRQ. If it fires between your column
|
|
281
|
+
write and your row read, the row byte belongs to whichever column SCNKEY
|
|
282
|
+
left selected, and its final `$7F` clobbers your walk. Either take over
|
|
283
|
+
the IRQ, or bracket the eight-column loop with `SEI`/`CLI`, or (the
|
|
284
|
+
recipe's choice) run with interrupts off and sync to a raster line
|
|
285
|
+
instead of the jiffy. The pitfall page's measurements put the main-loop
|
|
286
|
+
hazard on `$DC00` only, but a matrix scan writes `$DC00`, so it is exposed
|
|
287
|
+
where a plain joystick poll is not.
|
|
288
|
+
|
|
289
|
+
**Debouncing is not needed.** A mechanical contact bounces for a few
|
|
290
|
+
milliseconds when it closes (rung 4, not measured here); a scan once per
|
|
291
|
+
frame samples every 20 ms on PAL and 16.7 ms on NTSC (arithmetic from the
|
|
292
|
+
frame rates). The sample period is the debounce: the read lands inside a
|
|
293
|
+
bounce rarely, and when it does the worst case is one press event that
|
|
294
|
+
arrives a frame late, or one extra release-and-press pair, which a
|
|
295
|
+
per-key age counter absorbs since the counter resets to 0 and fires
|
|
296
|
+
again on the next frame. The KERNAL's SCNKEY does no bounce filtering
|
|
297
|
+
either; it keeps the last scan's matrix code in `$C5`
|
|
298
|
+
(`hardware/c64-memory-map.md`) and decides new-versus-repeat against it,
|
|
299
|
+
which is the same previous-scan comparison, not a timing filter (from the
|
|
300
|
+
ROM's variable layout; the comparison itself is not measured here). A scan
|
|
301
|
+
that runs many times per frame, for instance inside a tight wait loop,
|
|
302
|
+
does need a filter, because it can see both edges of a bounce.
|
|
303
|
+
|
|
304
|
+
### Variations
|
|
305
|
+
|
|
306
|
+
**Only the keys you use.** Drive just the columns holding the game's keys
|
|
307
|
+
and skip the rest; a WASD plus SPACE game needs columns 1, 2 and 7.
|
|
308
|
+
|
|
309
|
+
**Shift handling.** Left shift is column 1 row 7, right shift column 6
|
|
310
|
+
row 4 (`hardware/cia-reference.md`; Oscar64's `key_shift()` tests the
|
|
311
|
+
same two bits). Read them as ordinary keys and use them as modifiers in
|
|
312
|
+
software.
|
|
313
|
+
|
|
314
|
+
**No-key shortcut.** Drive all eight columns low (`$DC00` = `$00`) and read
|
|
315
|
+
`$DC01` once; `$FF` means no key and no port 1 input, and the eight-pass
|
|
316
|
+
scan can be skipped that frame.
|
|
317
|
+
|
|
318
|
+
### Recipes
|
|
319
|
+
|
|
320
|
+
- `recipes/oscar64/joystick-input.md`
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## paddle_read — Read the paddles through the SID's POTX/POTY with the CIA1 port select
|
|
325
|
+
|
|
326
|
+
**Complexity:** medium
|
|
327
|
+
**Region:** both
|
|
328
|
+
**Uses registers:** D419, D41A, DC00, DC01, DC02
|
|
329
|
+
**Cost:** cycles_per_frame=1152, zp_bytes=0, irq_slots=0
|
|
330
|
+
**Cost basis:** arithmetic
|
|
331
|
+
**Cost measured on:** kickassembler-paddle-read (two ports in one frame)
|
|
332
|
+
|
|
333
|
+
### Why
|
|
334
|
+
|
|
335
|
+
A paddle is a potentiometer, and the C64 reads it through the SID, not
|
|
336
|
+
the CIA. `$D419` (POTX) and `$D41A` (POTY) hold an 8-bit conversion of
|
|
337
|
+
the voltage on the two pot pins of one control port. Which port is a
|
|
338
|
+
choice the program makes through CIA1 port A: bit 6 of `$DC00` set
|
|
339
|
+
selects control port 1, bit 7 set selects port 2. Both ports carry two
|
|
340
|
+
pots, so a four-paddle game reads all four values through the same two
|
|
341
|
+
registers by switching the select and reading twice. The registers are
|
|
342
|
+
read-only; there is nothing to set up on the SID side.
|
|
343
|
+
|
|
344
|
+
### How
|
|
345
|
+
|
|
346
|
+
The direction register `$DC02` must be `$FF`, which is how IOINIT
|
|
347
|
+
leaves it; the select bits are outputs. Then, per port:
|
|
348
|
+
|
|
349
|
+
```text
|
|
350
|
+
$DC00 = $40 ; bit 6: control port 1 (or $80, bit 7: port 2)
|
|
351
|
+
wait ; a conversion has to start and finish after the switch
|
|
352
|
+
x1 = $D419 ; paddle 1 (port 1 pin 9)
|
|
353
|
+
y1 = $D41A ; paddle 2 (port 1 pin 5)
|
|
354
|
+
$DC00 = $80
|
|
355
|
+
wait
|
|
356
|
+
x2 = $D419
|
|
357
|
+
y2 = $D41A
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The wait is the whole difficulty. The SID converts on its own clock, one
|
|
361
|
+
conversion every 512 cycles, and the switch does not restart it. A read
|
|
362
|
+
taken before a conversion that began after the switch has completed
|
|
363
|
+
returns the old port's value or a byte from the middle of a conversion.
|
|
364
|
+
Measured in VICE x64sc 3.10 with a 1351 as the pot source, sampling
|
|
365
|
+
`$D419` every 8 cycles after the select write at eight phases 65 cycles
|
|
366
|
+
apart: at six phases the settled value was present within 480 cycles
|
|
367
|
+
(from cycle 476 at phase 0, on PAL and NTSC), at two phases it needed
|
|
368
|
+
more than 512 and was present within 544; nothing settled between 481
|
|
369
|
+
and 512. So in VICE 512 is the conversion period, not a safe delay from
|
|
370
|
+
the write. Wait at least 576 cycles, which is the measured worst case of
|
|
371
|
+
544 plus 32 of margin; that figure is measured in VICE, and the hardware
|
|
372
|
+
worst case after a mid-conversion switch is not measured here. Or use
|
|
373
|
+
the schedule most games use, select on one frame and read on the next:
|
|
374
|
+
a frame is 19,656 cycles on PAL and 17,095 on NTSC (arithmetic: 312
|
|
375
|
+
lines of 63 and 263 lines of 65), and both ports fit in two frames.
|
|
376
|
+
|
|
377
|
+
Reading both ports means alternating the select and paying the wait
|
|
378
|
+
each time. The cost line is two waits of 576 cycles, the recommended
|
|
379
|
+
wait, for a two-port read done in one frame; the reads and writes
|
|
380
|
+
themselves are under 30 cycles. A program that selects at the end of
|
|
381
|
+
its frame and reads at the start of the next spends none of that
|
|
382
|
+
waiting.
|
|
383
|
+
|
|
384
|
+
The paddle buttons are not on the SID. They are read on the port's
|
|
385
|
+
LEFT and RIGHT switch lines (bits 2 and 3 of the joystick byte: `$DC00`
|
|
386
|
+
for port 2, `$DC01` for port 1, low when pressed). That the paddle
|
|
387
|
+
buttons ride those two lines is not measured here, since a headless
|
|
388
|
+
VICE run cannot press one, and is not stated on
|
|
389
|
+
`hardware/cia-reference.md`, which places the paddle switches somewhere
|
|
390
|
+
in PA0-PA4. The recipe shows both bytes.
|
|
391
|
+
|
|
392
|
+
### Why it works
|
|
393
|
+
|
|
394
|
+
The two select bits drive the SID's analogue multiplexer directly from
|
|
395
|
+
the CIA pins; the SID sees one port's pots at a time and converts what
|
|
396
|
+
it sees. The usual account of the SID's converter, not measured here,
|
|
397
|
+
is that each conversion discharges a capacitor and then counts cycles
|
|
398
|
+
until the pot charges it again, which would make the result a cycle
|
|
399
|
+
count in 0 to 255 and the period 512 cycles, half to discharge and half
|
|
400
|
+
to count. The two-bucket settle pattern measured above is VICE's model
|
|
401
|
+
of that converter, and this page does not claim it for the chip. What
|
|
402
|
+
the measurement does show is that the register holds the last finished
|
|
403
|
+
conversion, whichever port that was for, until the next one completes.
|
|
404
|
+
Selecting neither port reads
|
|
405
|
+
`$FF`, as does an empty port, since the pin floats high (measured in
|
|
406
|
+
VICE; the same reading a paddle at one end of its travel gives, so a
|
|
407
|
+
game cannot tell an empty port from a paddle at full scale).
|
|
408
|
+
|
|
409
|
+
The keyboard scan interferes because it owns the same bits. SCNKEY, in
|
|
410
|
+
the jiffy IRQ, writes `$DC00` to drive the columns and leaves `$7F`
|
|
411
|
+
there when it is done: bit 6 set, bit 7 clear, control port 1
|
|
412
|
+
selected. A program that selected port 2 and is waiting for the
|
|
413
|
+
conversion has, after the interrupt, port 1 selected and does not know
|
|
414
|
+
it. Measured in VICE: 2,000 passes of select port 2, wait 530 cycles,
|
|
415
|
+
read, with the KERNAL IRQ live, returned something other than port 2's
|
|
416
|
+
value 53 to 59 times per run, about one per jiffy interrupt, and the
|
|
417
|
+
wrong values were port 1's or a mid-conversion byte. The same 2,000
|
|
418
|
+
passes under `SEI` were wrong 0 times. Three ways to time the read
|
|
419
|
+
against the scan: bracket select, wait and read with `SEI`/`CLI`; own
|
|
420
|
+
the IRQ, so no scan runs; or select and read at fixed raster lines and
|
|
421
|
+
keep the KERNAL scan's line, which is wherever the jiffy IRQ lands, out
|
|
422
|
+
of the window. Re-selecting immediately before the read, as
|
|
423
|
+
`hardware/sid-reference.md` suggests, is not enough on its own: the
|
|
424
|
+
conversion still needs its 512 cycles after that write.
|
|
425
|
+
|
|
426
|
+
The DDR matters for the same reason it matters for the keyboard:
|
|
427
|
+
`cia1_ddr_cleared_kills_keyboard` in `pitfalls/input.md`. With `$DC02`
|
|
428
|
+
cleared, the select bits are inputs, the write reaches no pin, and
|
|
429
|
+
both select bits read high: both ports selected, a case the SID page
|
|
430
|
+
does not define and this page does not measure (the recipe's `$C0` row
|
|
431
|
+
shows only what VICE does with it).
|
|
432
|
+
|
|
433
|
+
### Variations
|
|
434
|
+
|
|
435
|
+
**One port, no switching.** A single-port paddle game writes the select
|
|
436
|
+
once and never touches `$DC00` again, and then only the KERNAL scan can
|
|
437
|
+
disturb it. With the KERNAL IRQ off it can read `$D419` whenever it
|
|
438
|
+
likes.
|
|
439
|
+
|
|
440
|
+
**Interleaved frames.** Select port 1 on even frames and read it on odd
|
|
441
|
+
frames, port 2 the other way. Every paddle updates at half the frame
|
|
442
|
+
rate, which is enough for a bat, and no frame carries a wait.
|
|
443
|
+
|
|
444
|
+
**Position sweeps.** Not measured here: the host mouse that drives
|
|
445
|
+
VICE's paddles cannot be moved headlessly, so the value's range and
|
|
446
|
+
linearity against the knob are from `hardware/sid-reference.md`, which
|
|
447
|
+
gives the usable span as about `$00` to `$DF`.
|
|
448
|
+
|
|
449
|
+
### Cycle budget
|
|
450
|
+
|
|
451
|
+
The reads and writes are 4 cycles each. The whole cost is the wait
|
|
452
|
+
between a select and its first trustworthy read: 544 cycles at the
|
|
453
|
+
worst phase in the runs here, 576 with 32 of margin, which is just over
|
|
454
|
+
nine PAL raster lines. The Cost line states 1,152, two waits of 576, as
|
|
455
|
+
the worst frame of a two-port read done inside one frame.
|
|
456
|
+
|
|
457
|
+
### Recipes
|
|
458
|
+
|
|
459
|
+
- `recipes/kickassembler/paddle-read.md`
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## mouse_1351_read — Read a 1351 mouse's position counter from POTX/POTY once per frame
|
|
464
|
+
|
|
465
|
+
**Complexity:** medium
|
|
466
|
+
**Region:** both
|
|
467
|
+
**Uses registers:** D419, D41A, DC00, DC01, DC02
|
|
468
|
+
**Cost:** cycles_per_frame=104, zp_bytes=0, irq_slots=0
|
|
469
|
+
**Cost basis:** measured-vice
|
|
470
|
+
**Cost measured on:** kickassembler-mouse-1351-read (one call, worst case)
|
|
471
|
+
|
|
472
|
+
### Why
|
|
473
|
+
|
|
474
|
+
The Commodore 1351 in its proportional mode is not a joystick and not a
|
|
475
|
+
paddle, though it uses both sets of lines. Its movement arrives through
|
|
476
|
+
the SID's pot registers, `$D419` and `$D41A`, as a position counter the
|
|
477
|
+
mouse keeps itself; its two buttons arrive on the control port's switch
|
|
478
|
+
lines. A program that reads it as a paddle sees a value that wanders
|
|
479
|
+
and wraps; a program that reads it as a joystick sees nothing at all
|
|
480
|
+
unless the mouse was powered up in its joystick mode. The 1350, and the
|
|
481
|
+
1351 with the right button held at power-up, report movement as
|
|
482
|
+
joystick direction pulses on the switch lines instead; nothing below
|
|
483
|
+
applies to that mode.
|
|
484
|
+
|
|
485
|
+
### How
|
|
486
|
+
|
|
487
|
+
Select the mouse's port through CIA1 port A, bit 6 of `$DC00` for
|
|
488
|
+
control port 1 or bit 7 for port 2, with `$DC02` at `$FF` so the write
|
|
489
|
+
reaches the pins; that is the same select as `paddle_read`, and the
|
|
490
|
+
same 512-cycle conversion follows it. Then, once per frame:
|
|
491
|
+
|
|
492
|
+
```text
|
|
493
|
+
x = ($D419 >> 1) & $3F ; bits 1 to 6 are the counter, bit 0 is noise
|
|
494
|
+
dx = (x - prev_x) & $3F ; the difference modulo 64
|
|
495
|
+
if dx >= 32: dx = dx - 64 ; 0..31 forward, 32..63 back
|
|
496
|
+
prev_x = x
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
and the same for `$D41A`. The button byte is the port's joystick byte,
|
|
500
|
+
`$DC01` for port 1 or `$DC00` for port 2: the left button is on the
|
|
501
|
+
FIRE line, bit 4, and the right button on the UP line, bit 0, both low
|
|
502
|
+
when pressed.
|
|
503
|
+
|
|
504
|
+
The register format is from the 1351's documentation and is not
|
|
505
|
+
measured against a real mouse here. What is measured, in VICE x64sc
|
|
506
|
+
3.10 with its 1351 on port 1 (`recipes/kickassembler/mouse-1351-read.md`):
|
|
507
|
+
over 250 frames at rest the masked counter read 32 on every frame on
|
|
508
|
+
both axes and both models, while the raw byte alternated between `$40`
|
|
509
|
+
and `$41`, bit 0 set in 128, 112 and 112 of the 250 frames across three
|
|
510
|
+
runs. Bit 0 is noise from one frame to the next, not a constant offset,
|
|
511
|
+
and any code that compares raw pot bytes will see movement that is not
|
|
512
|
+
there. Shift it off before anything else.
|
|
513
|
+
|
|
514
|
+
### Why it works
|
|
515
|
+
|
|
516
|
+
The mouse counts its own quadrature transitions in a 6-bit counter per
|
|
517
|
+
axis and encodes the counter as a voltage on the pot line, timed against
|
|
518
|
+
the SID's conversion cycle, so that the byte the SID finishes every 512
|
|
519
|
+
cycles carries the counter in bits 1 to 6. The period is the SID's, not
|
|
520
|
+
the mouse's, and it is the figure `paddle_read` measured: 512 cycles a
|
|
521
|
+
conversion, about 520 microseconds on PAL and 500 on NTSC (arithmetic
|
|
522
|
+
from the clock rates). A frame is 38 conversions on PAL and 33 on
|
|
523
|
+
NTSC (arithmetic), so the register has been refreshed many times over
|
|
524
|
+
before the next read, and one read per frame loses nothing so long as
|
|
525
|
+
the counter has moved fewer than 32 steps in between. Whether a hand
|
|
526
|
+
can move a 1351 faster than that in one frame, a fiftieth of a second
|
|
527
|
+
on PAL and a sixtieth on NTSC, is not measured here; the delta arithmetic is exact below that speed and
|
|
528
|
+
wrong above it, with no way to tell from the data.
|
|
529
|
+
|
|
530
|
+
The modulo-64 subtraction is what makes the wrap invisible. 63 to 0 is
|
|
531
|
+
`(0 - 63) & $3F` = 1, a step forward; 0 to 63 is `(63 - 0) & $3F` = 63,
|
|
532
|
+
read as -1. The two half-turn cases, a difference of exactly 32, are
|
|
533
|
+
ambiguous by construction and the recipe's convention reads them as
|
|
534
|
+
-32. The recipe runs ten such pairs, including both wraps and both
|
|
535
|
+
half-turns, against expected values and prints `OK` for each.
|
|
536
|
+
|
|
537
|
+
The select bits are the ones `paddle_read` describes, with the same
|
|
538
|
+
consequence: the KERNAL's SCNKEY drives `$DC00` from the jiffy IRQ and
|
|
539
|
+
changes bit 6 as it walks the columns, so a conversion started during
|
|
540
|
+
the scan converts the wrong source. `paddle_read` measured about one
|
|
541
|
+
bad read per jiffy interrupt with the KERNAL IRQ live and none under
|
|
542
|
+
`SEI`; the mouse read is the same read on the same register and the
|
|
543
|
+
same three remedies apply, `SEI` around the read, owning the IRQ, or a
|
|
544
|
+
read on a raster line at least 576 cycles after the scan's last column
|
|
545
|
+
write. The recipe reads at raster line 250 with interrupts off. Because
|
|
546
|
+
both ports' pots go through the same two SID registers, a mouse and a
|
|
547
|
+
pair of paddles on the other port share the select and cannot be read
|
|
548
|
+
in the same conversion; `paddle_read`'s interleaved-frame schedule
|
|
549
|
+
handles the pair.
|
|
550
|
+
|
|
551
|
+
Both button lines are joystick lines, so `joystick2_scan_phantom_press`
|
|
552
|
+
and `cia1_ddr_cleared_kills_keyboard` in `pitfalls/input.md` apply to a
|
|
553
|
+
mouse in port 2 exactly as to a stick: read `$DC00` from the main loop
|
|
554
|
+
or from a handler that cannot interrupt the scan, and never clear
|
|
555
|
+
`$DC02` to do it. A mouse in port 1 has the keyboard problem the other
|
|
556
|
+
way round, and the select makes it worse: `$40` in `$DC00` sets bit 6
|
|
557
|
+
and clears the other seven, so keyboard columns 0 to 5 and 7 are driven
|
|
558
|
+
low for as long as port 1 is selected, and any held key in those
|
|
559
|
+
columns pulls its row line low on `$DC01`, where the mouse's right
|
|
560
|
+
button is row 0 (UP, PB0) and its left button row 4 (FIRE, PB4). That
|
|
561
|
+
is the wiring `keyboard_matrix_scan` describes for port 1 sticks, not
|
|
562
|
+
measured here with a mouse. A game that reads the keyboard as well as a
|
|
563
|
+
port 1 mouse selects with `$7F`, the value SCNKEY itself leaves behind:
|
|
564
|
+
bit 6 still set, only column 7 driven, so only that column's keys can
|
|
565
|
+
alias the mouse lines (1, left-arrow, CTRL, 2 and SPACE, measured for
|
|
566
|
+
port 1 in `pitfalls/input.md`). The recipe uses `$40` with no keyboard
|
|
567
|
+
in play.
|
|
568
|
+
|
|
569
|
+
### Variations
|
|
570
|
+
|
|
571
|
+
**Accumulated position.** Add each frame's `dx` and `dy` to a 16-bit
|
|
572
|
+
pointer position and clamp to the screen; the pointer sprite's
|
|
573
|
+
coordinate comes from that sum, never from the counter.
|
|
574
|
+
|
|
575
|
+
**Acceleration.** Scale `dx` by its own magnitude, doubling a delta of
|
|
576
|
+
8 or more, so slow moves are precise and fast ones cross the screen.
|
|
577
|
+
The threshold is a matter of feel and is not measured here.
|
|
578
|
+
|
|
579
|
+
**Half-rate reads.** Reading every other frame halves the cost and
|
|
580
|
+
halves the speed at which the 32-step ambiguity is reached; a game that
|
|
581
|
+
reads at half rate should say so in its own budget.
|
|
582
|
+
|
|
583
|
+
### Cycle budget
|
|
584
|
+
|
|
585
|
+
One call of the recipe's `read_mouse`, both axes with their deltas and
|
|
586
|
+
including `jsr` and `rts`, costs 102 to 104 cycles by the sign of each
|
|
587
|
+
axis's delta: a delta of 0 to +31 takes a `bcc` (3 cycles), a negative
|
|
588
|
+
delta falls through it and runs the `ora` sign-extend (2 plus 2), one
|
|
589
|
+
cycle more per axis. 102 is both non-negative, 103 one of each, 104
|
|
590
|
+
both negative. The worst case measured 104 cycles by CIA2 timer A
|
|
591
|
+
difference on raster line 250, on both models, with both previous
|
|
592
|
+
counters set to force the negative path (measured in VICE x64sc 3.10).
|
|
593
|
+
The Cost line states that figure. There is no wait in the per-frame
|
|
594
|
+
cost because the
|
|
595
|
+
select is written once and never changed; a program that switches the
|
|
596
|
+
select to read paddles on the other port pays `paddle_read`'s settle.
|
|
597
|
+
|
|
598
|
+
### Recipes
|
|
599
|
+
|
|
600
|
+
- `recipes/kickassembler/mouse-1351-read.md`
|
|
601
|
+
|
|
602
|
+
## attract_mode_input_replay — The title screen plays the game from a recorded input stream
|
|
603
|
+
|
|
604
|
+
**Complexity:** low
|
|
605
|
+
**Region:** both
|
|
606
|
+
**Uses registers:** DC00, D012
|
|
607
|
+
**Requires:** joystick_edge_detect, lfsr_random, frame_sync_loop
|
|
608
|
+
**Cost:** cycles_per_frame=68
|
|
609
|
+
**Cost basis:** measured-vice
|
|
610
|
+
**Cost measured on:** oscar64-attract-replay (longest step)
|
|
611
|
+
|
|
612
|
+
### Why
|
|
613
|
+
|
|
614
|
+
A title screen that idles is a still frame. The arcade answer is the
|
|
615
|
+
attract mode: after a while the game plays itself, and anyone watching
|
|
616
|
+
sees what the game is. The cheap way to get one is not a second code
|
|
617
|
+
path that moves the player about; it is the game itself, with its input
|
|
618
|
+
coming from somewhere other than the joystick port. The player code
|
|
619
|
+
reads one input byte. During play the port fills it. During the demo a
|
|
620
|
+
recording fills it. Nothing else changes, so the demo does exactly what
|
|
621
|
+
the game does, and a change to the game's rules is a change to the demo
|
|
622
|
+
for free.
|
|
623
|
+
|
|
624
|
+
### How
|
|
625
|
+
|
|
626
|
+
Route every input read through one byte. `joystick_edge_detect` already
|
|
627
|
+
keeps last frame's port byte; put this frame's byte beside it and have
|
|
628
|
+
the game read the pair, never `$DC00` directly. The main loop is the
|
|
629
|
+
only place that writes it: from the port on the title and in play, from
|
|
630
|
+
the recording during the demo.
|
|
631
|
+
|
|
632
|
+
Store the recording as run-length pairs: the port byte exactly as
|
|
633
|
+
`$DC00` gives it, active low, and the number of frames it was held. A
|
|
634
|
+
joystick changes a few times a second, so a 285-frame demo is eight
|
|
635
|
+
pairs. A count of zero ends the stream. Beside the stream keep the seed
|
|
636
|
+
the random generator had when the recording was made, and the state the
|
|
637
|
+
recording ends in, here the player's X and Y on its last frame, so the
|
|
638
|
+
program can check its own replay.
|
|
639
|
+
|
|
640
|
+
Count idle frames on the title: a byte that goes up once a frame while
|
|
641
|
+
the port reads nothing and back to zero on any bit. When it reaches the
|
|
642
|
+
attract delay, start the demo: reset the playfield the way a new game
|
|
643
|
+
would, set the LFSR to the recorded seed, and point the stream at its
|
|
644
|
+
first pair. Each demo frame, if the current pair has frames left, hold
|
|
645
|
+
its byte; otherwise load the next pair; when the next pair's count is
|
|
646
|
+
zero the demo is over and the title comes back.
|
|
647
|
+
|
|
648
|
+
Keep reading the port during the demo. Any bit low is a real press: end
|
|
649
|
+
the demo that frame, before the stream is consulted, and let the title
|
|
650
|
+
take over. The player then starts a game from the title as usual.
|
|
651
|
+
|
|
652
|
+
### Why it works
|
|
653
|
+
|
|
654
|
+
The replay is exact because the game is deterministic from its seed.
|
|
655
|
+
Its state on any frame is a function of the state before it and the
|
|
656
|
+
input byte, and the only source of variation is the random generator.
|
|
657
|
+
Reseed the generator, feed the same bytes in the same frames, and every
|
|
658
|
+
intermediate state is the one the recording saw, so the last frame lands
|
|
659
|
+
where the table says. The recipe shows the other side: built with the
|
|
660
|
+
reseed left out, the same 285 bytes end 210 pixels from the recorded
|
|
661
|
+
position, because the title screen had moved the generator on. The
|
|
662
|
+
frame is the unit that makes "the same frames" true on both regions: a
|
|
663
|
+
`frame_sync_loop` runs one step per frame, so a count of 50 is 50 steps
|
|
664
|
+
on PAL and on NTSC, in less wall time on NTSC and with the same result.
|
|
665
|
+
|
|
666
|
+
The seed must not be zero. An LFSR at zero stays at zero, so the reseed
|
|
667
|
+
maps zero to one; a recording tool should never store zero, and the
|
|
668
|
+
guard costs one compare.
|
|
669
|
+
|
|
670
|
+
### Variations
|
|
671
|
+
|
|
672
|
+
Öörni's control override, named on `game-design/game-structure.md`, is
|
|
673
|
+
the same seam used the other way: enemies and cutscene actors read a
|
|
674
|
+
virtual joystick byte that the AI or the script writes, and a
|
|
675
|
+
conversation freezes the player by writing zero. A recorded human run is
|
|
676
|
+
the natural source for the stream: log the byte and the frame count
|
|
677
|
+
through the same variable during a real game and dump the pairs. The
|
|
678
|
+
verdict constants are then whatever the recording ended on, which is how
|
|
679
|
+
a shipped game can check its own attract mode after a rules change. A
|
|
680
|
+
longer attract can chain several recordings, or a recorded run and a
|
|
681
|
+
scripted one, through the same byte.
|
|
682
|
+
|
|
683
|
+
### Cycle budget
|
|
684
|
+
|
|
685
|
+
The replay step, the code that decides whether to hold the current pair
|
|
686
|
+
or load the next one and writes the input byte, was timed with CIA1
|
|
687
|
+
timer B, interrupts held off for the timed window, over a 285-frame
|
|
688
|
+
demo. The longest step was 68 cycles on PAL and NTSC and in all three
|
|
689
|
+
builds, and that figure includes the timer's own start and stop stores.
|
|
690
|
+
The Cost line states it; the game step it feeds is the game's own cost,
|
|
691
|
+
not the technique's. The idle counter on the title is one increment and
|
|
692
|
+
one compare a frame. Measured in VICE x64sc 3.10 on the recipe's
|
|
693
|
+
listing.
|
|
694
|
+
|
|
695
|
+
### Recipes
|
|
696
|
+
|
|
697
|
+
- `recipes/oscar64/attract-replay.md`
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## four_player_read — Read joysticks 3 and 4 through a user-port 4-player adapter
|
|
702
|
+
|
|
703
|
+
**Complexity:** low
|
|
704
|
+
**Region:** both
|
|
705
|
+
**Uses registers:** DC00, DC01, DD01, DD03
|
|
706
|
+
**Cost:** cycles_per_frame=62, irq_slots=0
|
|
707
|
+
**Cost basis:** measured-vice
|
|
708
|
+
**Cost measured on:** kickassembler-four-player-read (read inlined, once a frame)
|
|
709
|
+
|
|
710
|
+
### Why
|
|
711
|
+
|
|
712
|
+
The C64 has two control ports. Party and sports games for four players
|
|
713
|
+
add two more joysticks through an adapter on the user port, read through
|
|
714
|
+
CIA2 port B (`$DD01`). The common design, sold by Protovision and
|
|
715
|
+
emulated by VICE as the "CGA userport joy adapter", has seven port-B
|
|
716
|
+
lines (six inputs and a select) for ten switches, so it multiplexes the directions and gives each
|
|
717
|
+
fire button its own line. A game has to know which bit is which, and
|
|
718
|
+
that the adapter types are not interchangeable.
|
|
719
|
+
|
|
720
|
+
### How
|
|
721
|
+
|
|
722
|
+
Set PB7 as an output once. It is the adapter's select line; PB0 to PB6
|
|
723
|
+
stay inputs:
|
|
724
|
+
|
|
725
|
+
```text
|
|
726
|
+
$DD03 = $80 ; DDR B: PB7 out, PB0-PB6 in
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Then, each frame:
|
|
730
|
+
|
|
731
|
+
```text
|
|
732
|
+
j1 = $DC01 & $1F ; port 1
|
|
733
|
+
j2 = $DC00 & $1F ; port 2 ($DC00 must drive no keyboard column)
|
|
734
|
+
$DD01 = $80 ; PB7 = 1: joystick 3's directions on PB0-PB3
|
|
735
|
+
j3 = $DD01 & $1F ; PB0-PB3 directions, PB4 = fire 3
|
|
736
|
+
$DD01 = $00 ; PB7 = 0: joystick 4's directions on PB0-PB3
|
|
737
|
+
v = $DD01
|
|
738
|
+
j4 = (v & $0F) | ((v & $20) >> 1) ; PB5 = fire 4, moved to bit 4
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
All four bytes then have the `$DC00` layout: bit 0 up, 1 down, 2 left,
|
|
742
|
+
3 right, 4 fire, 0 when pressed. `joystick_edge_detect` and the rest of
|
|
743
|
+
a game's input code work on them unchanged.
|
|
744
|
+
|
|
745
|
+
Fire 3 on PB4 and fire 4 on PB5 are present under either select. Only
|
|
746
|
+
the directions go through the select. Joystick 4's fire therefore is not
|
|
747
|
+
bit 4 of its read; forgetting the shift reads joystick 4 as never firing.
|
|
748
|
+
|
|
749
|
+
### Why it works
|
|
750
|
+
|
|
751
|
+
Writing `$DD01` with `$DD03` at `$80` drives only PB7; the input bits
|
|
752
|
+
ignore the write. PB7 drives a 74LS257 multiplexer (VICE's pin table)
|
|
753
|
+
that routes one joystick's four direction lines to PB0-PB3. Protovision
|
|
754
|
+
and the icomp wiki both give PB7 = 1 for joystick 3 and PB7 = 0 for
|
|
755
|
+
joystick 4, with fire 4 on PB5. VICE 3.10's source agrees
|
|
756
|
+
(`src/userport/userport_joystick.c`): PB7 high selects joystick 3's
|
|
757
|
+
directions, and the read carries joystick 3's fire on PB4 and joystick
|
|
758
|
+
4's on PB5 whatever the select.
|
|
759
|
+
|
|
760
|
+
What was run: the recipe, in VICE x64sc 3.10 with the CGA adapter and
|
|
761
|
+
autofire on joysticks 3 and 4 at 5 and 7 presses a second, counted 11
|
|
762
|
+
and 15 presses in 100 PAL frames, 9 and 12 in 100 NTSC frames, and
|
|
763
|
+
found PB4 and PB5 equal under both selects on every frame. Directions on
|
|
764
|
+
joysticks 3 and 4 were not injected: VICE has no headless way to move
|
|
765
|
+
them. The select's effect on PB0-PB3 is therefore from the two sources
|
|
766
|
+
and the VICE source, not measured here.
|
|
767
|
+
|
|
768
|
+
VICE switches the multiplexer at the store, so a load 4 cycles later
|
|
769
|
+
sees the new joystick. No settle time on hardware is measured here.
|
|
770
|
+
|
|
771
|
+
### Variations
|
|
772
|
+
|
|
773
|
+
**Kingsoft adapter.** A different wiring with no select line. In VICE's
|
|
774
|
+
source (`src/userport/userport_hks_joystick.c`, not exercised here
|
|
775
|
+
beyond one control run) joystick 4's directions are on PB0-PB3 in the
|
|
776
|
+
order right, left, down, up; joystick 3's fire is PB4, its right, left
|
|
777
|
+
and down are PB5-PB7 and its up is PA2, bit 2 of `$DD00`; joystick 4's
|
|
778
|
+
fire arrives on CIA2's serial data pin SP2, clocked from CIA1's CNT1. The
|
|
779
|
+
control run in the recipe read a Kingsoft adapter with the CGA code:
|
|
780
|
+
joystick 3's fire counted, joystick 4's never did. VICE also emulates
|
|
781
|
+
HIT and StarByte adapters with other wirings. A game that supports more
|
|
782
|
+
than one adapter needs a menu choice or a per-type read.
|
|
783
|
+
|
|
784
|
+
**Detecting the adapter.** With no adapter, PB0-PB6 read high under
|
|
785
|
+
both selects (measured in VICE, the recipe's control run with no
|
|
786
|
+
user-port device), which is also what a connected adapter with every
|
|
787
|
+
joystick idle returns.
|
|
788
|
+
There is no reliable detect; ask the player.
|
|
789
|
+
|
|
790
|
+
**Two reads a frame for responsiveness.** The read is 62 cycles, so a
|
|
791
|
+
game that polls twice a frame, for example once in the IRQ and once in
|
|
792
|
+
the main loop, spends 124.
|
|
793
|
+
|
|
794
|
+
### Conflicts on the user port
|
|
795
|
+
|
|
796
|
+
The adapter uses PB0-PB5 and PB7, and its DDR setting owns port B, so it cannot share the port with anything else
|
|
797
|
+
that uses port B. `hardware/cia-reference.md` lists the KERNAL RS-232
|
|
798
|
+
driver (RXD sampled on `$DD01` bit 0 from its NMI) and user-port parallel
|
|
799
|
+
cables for 1541 fastloaders. Either one active at the same time would
|
|
800
|
+
corrupt the other's reads or writes (not measured here), and the adapter's `$DD03` setting is lost
|
|
801
|
+
if the other code rewrites the DDR. The IEC-bus loaders this repository
|
|
802
|
+
covers, Krill and Sparkle, are described in `pitfalls/loader.md` as
|
|
803
|
+
driving `$DD00` only; no `$DD01` use is stated for them, and none was
|
|
804
|
+
measured. A parallel-cable build of a loader is a different case and is
|
|
805
|
+
not covered here.
|
|
806
|
+
|
|
807
|
+
Ports 1 and 2 share `$DC00` and `$DC01` with the keyboard. The KERNAL
|
|
808
|
+
scan rewrites `$DC00`; `pitfalls/input.md` has what that does to a
|
|
809
|
+
port 2 read.
|
|
810
|
+
|
|
811
|
+
### Cycle budget
|
|
812
|
+
|
|
813
|
+
The read is 22 instructions and no branch: 62 cycles every frame,
|
|
814
|
+
measured by CIA1 timer A in VICE on PAL and NTSC, and the same from the
|
|
815
|
+
instruction table. As a subroutine it is 74 with the `jsr` and `rts`
|
|
816
|
+
(arithmetic). The Cost line states 62, the read inlined. The one-off
|
|
817
|
+
`$DD03` write is not part of the frame.
|
|
818
|
+
|
|
819
|
+
### Sources
|
|
820
|
+
|
|
821
|
+
- Protovision, 4-player adapter build page:
|
|
822
|
+
https://www.protovision.games/hardw/build4player.php?language=en
|
|
823
|
+
(PB7 select, `$DD03` = `$80`, fire 4 on bit 5)
|
|
824
|
+
- icomp wiki, "4 Player Adapter": https://wiki.icomp.de/wiki/4_Player_Adapter
|
|
825
|
+
(DDR `%10000000`, `$80` selects joystick 3, `$00` joystick 4, fire 4 on PB5)
|
|
826
|
+
- VICE 3.10 source, `src/userport/userport_joystick.c` (CGA adapter) and
|
|
827
|
+
`src/userport/userport_hks_joystick.c` (HIT, Kingsoft, StarByte)
|
|
828
|
+
|
|
829
|
+
### Recipes
|
|
830
|
+
|
|
831
|
+
- `recipes/kickassembler/four-player-read.md`
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## irq_keyboard_own_scan — Scan the keyboard from your own IRQ, with per-key age counters and the KERNAL's exit points
|
|
836
|
+
|
|
837
|
+
**Complexity:** medium
|
|
838
|
+
**Region:** both
|
|
839
|
+
**Uses registers:** DC00, DC01, DC02, DC03
|
|
840
|
+
**Requires:** keyboard_matrix_scan
|
|
841
|
+
**Cost:** cycles_per_frame=945, irq_slots=1, zp_bytes=1
|
|
842
|
+
**Cost basis:** measured-vice
|
|
843
|
+
|
|
844
|
+
### Why
|
|
845
|
+
|
|
846
|
+
A game that takes the IRQ vector and masks CIA1 has switched off
|
|
847
|
+
SCNKEY, so `GETIN`, the buffer at `$0277` and the STOP key all go
|
|
848
|
+
quiet; the fix a platformer built for a blind test needed on 2026-09-22
|
|
849
|
+
(`pitfalls/input.md`, `cia1_ddr_cleared_kills_keyboard`) is the general
|
|
850
|
+
one. Scan the matrix yourself, once a frame, from the same IRQ that
|
|
851
|
+
drives the game. Done there rather than in the main loop, the scan
|
|
852
|
+
cannot be torn by the KERNAL's column writes, runs at a fixed point in
|
|
853
|
+
the frame, and gives every key a frame-accurate history: pressed this
|
|
854
|
+
frame, held N frames, released this frame.
|
|
855
|
+
|
|
856
|
+
### How
|
|
857
|
+
|
|
858
|
+
1. Mask CIA1 (`$DC0D` = `$7F`, then read it) and take `$0314`, or
|
|
859
|
+
`$FFFE` with the KERNAL out. The direction registers stay as IOINIT
|
|
860
|
+
left them, `$DC02` = `$FF` and `$DC03` = `$00`; write them anyway,
|
|
861
|
+
because a loader or a previous program may not have.
|
|
862
|
+
2. In the IRQ, walk a single zero bit across `$DC00` from `$FE` to
|
|
863
|
+
`$7F` and read `$DC01` after each store into an eight-byte image,
|
|
864
|
+
one byte a column, bit r clear for a closed switch on row r. The
|
|
865
|
+
layout is in `hardware/cia-reference.md`.
|
|
866
|
+
3. Write `$FF` back to `$DC00`. A joystick 2 poll later in the frame,
|
|
867
|
+
or in the same handler, then reads only the port's own switches. The
|
|
868
|
+
KERNAL leaves `$7F` and a game that copies that habit sees column 7's
|
|
869
|
+
keys on port 2.
|
|
870
|
+
4. Edges: `prev EOR cur` is the set of changed bits; `AND prev` keeps
|
|
871
|
+
the presses, `AND cur` the releases. Then `prev` = `cur`.
|
|
872
|
+
5. Ages: for each key the game uses, a byte that is 0 while the key is
|
|
873
|
+
up and counts up from 1 while it is down, saturating at 255. Age 1 is
|
|
874
|
+
the press event; age N is "held N frames"; a repeat rule on the age
|
|
875
|
+
(first at REPEAT_AT, then every second frame) is `joystick_autorepeat`
|
|
876
|
+
applied to a key.
|
|
877
|
+
6. Leave through the KERNAL's exit that matches what you kept. Read from
|
|
878
|
+
the ROM image: `$EA81` is `PLA TAY PLA TAX PLA RTI`, the bare exit;
|
|
879
|
+
`$EA7E` is `LDA $DC0D` then the same, a bare exit that also
|
|
880
|
+
acknowledges CIA1; `$EA7B` is `JSR $EA87`, the SCNKEY call, then the
|
|
881
|
+
two above; `$EA31` is the whole service routine, jiffy clock and
|
|
882
|
+
cursor blink and tape motor and scan. A handler that keeps the KERNAL
|
|
883
|
+
jiffy IRQ alive and adds its own scan exits through `$EA7E`, or does
|
|
884
|
+
the acknowledge itself and uses `$EA81`; jumping to `$EA7B` runs the
|
|
885
|
+
KERNAL scan too and defeats the point.
|
|
886
|
+
|
|
887
|
+
### Why it works
|
|
888
|
+
|
|
889
|
+
There are no diodes in the matrix, so one column pulled low lets only
|
|
890
|
+
that column's closed switches pull rows down; eight passes see all 64
|
|
891
|
+
switches (`keyboard_matrix_scan`). SCNKEY can no longer interleave its
|
|
892
|
+
own column writes with yours because it is not running: it is only
|
|
893
|
+
ever called from the handler at `$EA31`, and masking CIA1 stops that
|
|
894
|
+
handler. Everything the KERNAL did with the result, the decode to
|
|
895
|
+
PETSCII, the modifier tables, the ten-byte buffer, the repeat delay
|
|
896
|
+
in `$028B/$028C`, is now the game's to do or to skip; a game that
|
|
897
|
+
steers with keys skips all of it and reads the image directly.
|
|
898
|
+
|
|
899
|
+
The age counter carries the whole per-key state in one byte. It is
|
|
900
|
+
also the debounce, for the reason `keyboard_matrix_scan` gives: at one
|
|
901
|
+
sample per frame, a bounce costs at worst one extra release-and-press
|
|
902
|
+
pair, which shows as a second age 1.
|
|
903
|
+
|
|
904
|
+
### Variations
|
|
905
|
+
|
|
906
|
+
**Ghosting with three keys.** Three closed switches at three corners of
|
|
907
|
+
a rectangle in the matrix make the fourth corner read closed. A
|
|
908
|
+
key-steered game picks its keys so no three of them form three corners
|
|
909
|
+
of a rectangle: SPACE, Z, C, B are all row 4 and can never ghost with
|
|
910
|
+
each other, but Z (column 1, row 4) with W (column 1, row 1) and S
|
|
911
|
+
(column 1, row 5) is fine while Z, W and R (column 2, row 1) ghosts C
|
|
912
|
+
(column 2, row 4) on. From the wiring, not measured: a headless run
|
|
913
|
+
cannot hold three keys.
|
|
914
|
+
|
|
915
|
+
**The shift and control lines.** Left shift is column 1 row 7, right
|
|
916
|
+
shift column 6 row 4, CTRL column 7 row 2, C= column 7 row 5. They are
|
|
917
|
+
ordinary switches to the scan; a game reads them as modifiers by
|
|
918
|
+
testing those bits alongside the key. SHIFT LOCK is left shift held
|
|
919
|
+
mechanically.
|
|
920
|
+
|
|
921
|
+
**A keyboard-driven menu.** Ages give a menu its repeat for free: move
|
|
922
|
+
the cursor when a direction key's age is 1, again when it is
|
|
923
|
+
REPEAT_AT, then every second frame. The press set gives one event per
|
|
924
|
+
key per press for the confirm key, whatever the frame rate.
|
|
925
|
+
|
|
926
|
+
**Keeping the KERNAL IRQ.** If the game needs the jiffy clock or the
|
|
927
|
+
KERNAL's STOP handling, leave `$DC0D` alone, scan in your own raster
|
|
928
|
+
IRQ and exit through `$EA81` after acknowledging `$D019`; the KERNAL's
|
|
929
|
+
own SCNKEY still runs on the CIA timer and still leaves `$7F` on
|
|
930
|
+
`$DC00`. A joystick 2 read then has to tolerate `$7F`, or write `$FF`
|
|
931
|
+
first, and the two scans' column writes can interleave if the raster
|
|
932
|
+
IRQ is allowed to pre-empt the timer one (`pitfalls/input.md`,
|
|
933
|
+
`joystick2_scan_phantom_press`, and `keyboard_matrix_scan`, "Why the
|
|
934
|
+
KERNAL IRQ must be out of the way").
|
|
935
|
+
|
|
936
|
+
### Cycle budget
|
|
937
|
+
|
|
938
|
+
Measured in VICE x64sc 3.10 with CIA1 Timer A in the recipe's
|
|
939
|
+
handler, PAL and NTSC alike: the scan alone, eight columns with the
|
|
940
|
+
`$FF` restore, is 252 cycles; the scan plus the `$DC00` read-back, the
|
|
941
|
+
edge pass over eight columns and the ageing of four keys is 781 with
|
|
942
|
+
the matrix empty and 945 with all four watched keys held (41 cycles a
|
|
943
|
+
held key, the longer ageing branch). The Cost line carries the 945,
|
|
944
|
+
the worst frame. On PAL that is under 5 % of a 19,656-cycle frame,
|
|
945
|
+
about fifteen raster lines; it runs from line 250 in the recipe, below
|
|
946
|
+
the display, where no badline can stretch it.
|
|
947
|
+
|
|
948
|
+
### Recipes
|
|
949
|
+
|
|
950
|
+
- `recipes/kickassembler/own-keyscan.md`
|
|
951
|
+
|
|
952
|
+
## light_pen_read — Read the light pen's latched beam position from LPX/LPY once per frame
|
|
953
|
+
|
|
954
|
+
**Complexity:** low
|
|
955
|
+
**Region:** both
|
|
956
|
+
**Uses registers:** D013, D014, D019, D01A, DC01
|
|
957
|
+
**Cost:** cycles_per_frame=94, zp_bytes=0, irq_slots=1
|
|
958
|
+
**Cost basis:** measured-vice
|
|
959
|
+
|
|
960
|
+
### Why
|
|
961
|
+
|
|
962
|
+
A light pen points at the screen and the machine has to say which cell
|
|
963
|
+
or pixel it is over. The pen has no position of its own: it fires a
|
|
964
|
+
pulse when the beam passes under it, and the VIC turns that pulse into a
|
|
965
|
+
coordinate by copying its own counters into `$D013` and `$D014`. A
|
|
966
|
+
program that reads those two registers at the right time, once a frame,
|
|
967
|
+
knows where the pen is; a program that reads them at the wrong time or
|
|
968
|
+
skips the correction reads a point a few cells to the right of the pen,
|
|
969
|
+
or the same stale point every frame.
|
|
970
|
+
|
|
971
|
+
### How
|
|
972
|
+
|
|
973
|
+
Enable the pen interrupt, bit 3 of `$D01A`, with the pen in control
|
|
974
|
+
port 1. When the pen's pulse pulls the LP pin low the VIC latches the
|
|
975
|
+
beam's X coordinate divided by two into `$D013` (LPX) and the low eight
|
|
976
|
+
bits of the raster line into `$D014` (LPY), and sets bit 3 of `$D019`.
|
|
977
|
+
The handler acknowledges by writing bit 3 back to `$D019`, copies the
|
|
978
|
+
two registers into program variables and counts the interrupt. The
|
|
979
|
+
main loop converts, once per frame:
|
|
980
|
+
|
|
981
|
+
```text
|
|
982
|
+
x = (LPX - PEN_DELAY) * 2 ; VIC X coordinate of the beam
|
|
983
|
+
col = (x - 24) / 8 ; text area starts at X = 24
|
|
984
|
+
row = (LPY - 51) / 8 ; text area starts at line 51
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
and treats anything outside 0 to 39 or 0 to 24 as "not on the text
|
|
988
|
+
area". Whether the pen is pressed to the screen, on pens that have a tip
|
|
989
|
+
switch, comes from the port's switch lines on `$DC01`: bit 4 is the
|
|
990
|
+
FIRE line, which is the pen's own trigger line. A program that only wants
|
|
991
|
+
to know whether the pen triggered this frame can poll bit 3 of `$D019`
|
|
992
|
+
instead of taking the interrupt, and must still write it back to clear
|
|
993
|
+
it.
|
|
994
|
+
|
|
995
|
+
`PEN_DELAY` is the pen's own latency in LPX units. A pen reports the
|
|
996
|
+
beam a little after the beam lit the phosphor under it, and the latch
|
|
997
|
+
takes the counter at the moment of the pulse, so LPX reads a constant
|
|
998
|
+
too large; the constant depends on the pen and the display, so the
|
|
999
|
+
program calibrates it once by asking for a touch on a known cell and
|
|
1000
|
+
keeping the difference. This is the standard treatment (rung 5, not
|
|
1001
|
+
measured here: no pen was available). Both figures and the reading
|
|
1002
|
+
pattern are measured in `recipes/kickassembler/light-pen-read.md`.
|
|
1003
|
+
|
|
1004
|
+
### Why it works
|
|
1005
|
+
|
|
1006
|
+
The latch fires once per frame: the first LP edge after the vertical
|
|
1007
|
+
blank copies the counters, later edges in the same frame are ignored,
|
|
1008
|
+
and the latch is reset at the start of the next frame
|
|
1009
|
+
(`hardware/vic-ii-reference.md`, "Light pen latch"). One read per frame
|
|
1010
|
+
therefore sees every position the pen reported, and reading twice in a
|
|
1011
|
+
frame sees the same value twice. The X counter is halved to fit eight
|
|
1012
|
+
bits, which is why LPX is in two-pixel units and a pen cannot resolve a
|
|
1013
|
+
single pixel horizontally; LPY is a raster line, so vertical resolution
|
|
1014
|
+
is one line and the text row needs only the divide by eight.
|
|
1015
|
+
|
|
1016
|
+
The LP pin is CIA1 PB4, control port 1's FIRE line. That is why the pen
|
|
1017
|
+
lives in port 1 and why a joystick fire press in port 1 latches a
|
|
1018
|
+
position too: with no pen attached `$D013` and `$D014` hold whatever the
|
|
1019
|
+
last fire press latched. It is also why the port-1 keyboard hazard
|
|
1020
|
+
applies. The KERNAL's SCNKEY drives the keyboard columns from `$DC00`
|
|
1021
|
+
in the jiffy IRQ, and a held key in a driven column pulls its row line
|
|
1022
|
+
low on `$DC01`; the pen's line is row 4 (`joystick2_scan_phantom_press`
|
|
1023
|
+
in `pitfalls/input.md`, Fix C, covers the port-1 form). A held SPACE or
|
|
1024
|
+
another row-4 key can therefore read as the pen's switch, and the scan
|
|
1025
|
+
itself cannot pull the LP pin, which is an input to the VIC, so it
|
|
1026
|
+
cannot fake a latch; the recipe turns the CIA1 interrupt off anyway.
|
|
1027
|
+
|
|
1028
|
+
Measured in VICE x64sc 3.10 with the recipe's pen (`-controlport1device
|
|
1029
|
+
11`), PAL and NTSC: over 300 frames the pen interrupt fired zero times,
|
|
1030
|
+
`$D013` and `$D014` read `$00` on every frame, bit 3 of `$D019` was
|
|
1031
|
+
clear at the end and `$DC01` read `$FF`. VICE's pen follows the host
|
|
1032
|
+
mouse and a headless run has none to move, so the emulated pen never
|
|
1033
|
+
triggers; what a triggering pen latches, and the frame-to-frame spread
|
|
1034
|
+
of the latch, is not measured here. The recipe's synthetic table checks
|
|
1035
|
+
the conversion on both corners of the text area, one step outside each,
|
|
1036
|
+
and the all-zero resting value, which converts to "outside".
|
|
1037
|
+
|
|
1038
|
+
### Variations
|
|
1039
|
+
|
|
1040
|
+
**A menu by pen.** Draw the choices as text cells, convert the latch to
|
|
1041
|
+
a row each frame, highlight that row, and take the tip switch on `$DC01`
|
|
1042
|
+
bit 4, or a second's dwell counted in frames, as the selection. The
|
|
1043
|
+
conversion is the recipe's `lpy_to_row`; the pen's vertical reading is
|
|
1044
|
+
the steadier axis, so a menu of rows needs no calibration in X.
|
|
1045
|
+
|
|
1046
|
+
**Polled instead of interrupt-driven.** Read bit 3 of `$D019` once a
|
|
1047
|
+
frame from the main loop, and read the two registers when it is set;
|
|
1048
|
+
write the bit back to clear it. The cost falls to a handful of cycles
|
|
1049
|
+
and no interrupt slot, at the price of reading the latch up to a frame
|
|
1050
|
+
late, which a menu does not notice.
|
|
1051
|
+
|
|
1052
|
+
**Averaging.** A real pen's LPX can differ by a unit or two from one
|
|
1053
|
+
frame to the next (rung 5, not measured here). Averaging the last four
|
|
1054
|
+
frames' values before the conversion steadies a cursor.
|
|
1055
|
+
|
|
1056
|
+
### Cycle budget
|
|
1057
|
+
|
|
1058
|
+
The interrupt, from the 6510 taking it to the `rti`, costs 94 cycles on
|
|
1059
|
+
both models: 7 for the interrupt sequence, 29 for the KERNAL's
|
|
1060
|
+
dispatcher from `$FF48` to the `$0314` vector, 36 for the handler's nine
|
|
1061
|
+
instructions and 22 for the exit through `$EA81` (measured in VICE x64sc
|
|
1062
|
+
3.10 by CIA2 timer A difference around a fixed loop with and without the
|
|
1063
|
+
interrupt, the same handler entered from a raster interrupt because the
|
|
1064
|
+
pen line cannot be pulled low by software; the four terms are
|
|
1065
|
+
arithmetic that agrees with it). The Cost line states the 94, one
|
|
1066
|
+
interrupt a frame. The conversion in the main loop is about 26 cycles for the
|
|
1067
|
+
column and 20 for the row by the instruction table (arithmetic), and is
|
|
1068
|
+
not on the line.
|
|
1069
|
+
|
|
1070
|
+
### Recipes
|
|
1071
|
+
|
|
1072
|
+
- `recipes/kickassembler/light-pen-read.md`
|