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,1292 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: scroll
|
|
3
|
+
chip: VIC-II
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- doc-type: technique-reference -->
|
|
7
|
+
|
|
8
|
+
# Scroll Techniques
|
|
9
|
+
|
|
10
|
+
The VIC-II provides two hardware scroll registers that shift the visible
|
|
11
|
+
display area up to 7 pixels in either axis without touching screen RAM.
|
|
12
|
+
Used alone, each register gives only eight positions — seven pixels of
|
|
13
|
+
travel — before it wraps (an earlier version said "one pixel of range per
|
|
14
|
+
frame", conflating the range with the 1 px/frame step rate). The real power comes from combining the hardware offset with
|
|
15
|
+
timed screen-RAM rotation: the hardware register handles sub-character
|
|
16
|
+
granularity while a CPU-side copy updates the coarser character grid. The
|
|
17
|
+
techniques in this document cover the full range from a single-axis
|
|
18
|
+
1-pixel-per-frame scroller to parallax depth effects and bitmap-mode
|
|
19
|
+
horizontal panning.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## soft_scroll_h — Hardware horizontal soft-scroll
|
|
24
|
+
|
|
25
|
+
**Complexity:** low
|
|
26
|
+
**Region:** both
|
|
27
|
+
**Uses registers:** D016
|
|
28
|
+
**Uses kernal:** (none)
|
|
29
|
+
**Cost:** cycles_per_frame=74041
|
|
30
|
+
**Cost basis:** measured-vice
|
|
31
|
+
**Cost measured on:** oscar64-soft-scroll-h (carry frame: a 25-row memmove of screen and colour RAM)
|
|
32
|
+
**Cost includes:** char_scroll_buffer_h
|
|
33
|
+
|
|
34
|
+
### Why
|
|
35
|
+
|
|
36
|
+
Games and demos frequently need to shift the entire display left or right
|
|
37
|
+
by one pixel at a time — a news ticker, a side-scrolling landscape, a
|
|
38
|
+
credit scroll. Doing this by rewriting every byte of screen RAM each frame
|
|
39
|
+
is prohibitively expensive. The VIC-II's $D016 XSCROLL field solves this
|
|
40
|
+
by shifting the pixel output pipeline before it reaches the border logic,
|
|
41
|
+
so the chip does the work in hardware at zero CPU cost per pixel column.
|
|
42
|
+
|
|
43
|
+
### How
|
|
44
|
+
|
|
45
|
+
$D016 bits 2-0 (XSCROLL) hold a three-bit fine-scroll offset. Each frame,
|
|
46
|
+
increment (or decrement) the stored XSCROLL value and write the new three
|
|
47
|
+
bits into $D016, preserving the CSEL and MCM bits in bits 3 and 4. The
|
|
48
|
+
display shifts by the number of pixels indicated without any change to
|
|
49
|
+
screen RAM or color RAM.
|
|
50
|
+
|
|
51
|
+
XSCROLL = n places the display n pixels to the RIGHT of the character grid
|
|
52
|
+
(measured in VICE x64sc: a block at column 0 sat at x 32-39 with XSCROLL=0
|
|
53
|
+
and at x 36-43 with XSCROLL=4). An earlier version of the next two
|
|
54
|
+
paragraphs had the directions reversed.
|
|
55
|
+
|
|
56
|
+
To move the display content to the right, increment XSCROLL each frame:
|
|
57
|
+
0 → 1 → 2 → ... → 7, then roll to 0 and simultaneously shift the
|
|
58
|
+
screen-RAM columns one character to the right.
|
|
59
|
+
|
|
60
|
+
To move the display content to the left (the usual "scrolling" direction
|
|
61
|
+
for a ticker), decrement XSCROLL: 7 → 6 → ... → 0, then roll to 7 and
|
|
62
|
+
shift screen-RAM one column to the left.
|
|
63
|
+
|
|
64
|
+
The XSCROLL = 0 case produces no visual shift relative to the character
|
|
65
|
+
grid. XSCROLL = 7 shifts seven pixels, placing the display one pixel
|
|
66
|
+
short of a full character-width offset.
|
|
67
|
+
|
|
68
|
+
### Why it works
|
|
69
|
+
|
|
70
|
+
The VIC-II's horizontal pixel sequencer ordinarily starts drawing at a
|
|
71
|
+
fixed position aligned to the character grid. XSCROLL delays the start of
|
|
72
|
+
the visible area within each 8-pixel character cell by the specified number
|
|
73
|
+
of clock cycles (one clock per pixel). Because the delay applies globally
|
|
74
|
+
to the entire display, every column shifts uniformly. The border logic and
|
|
75
|
+
the display-window start position are unaffected, so the column count
|
|
76
|
+
remains 40 and the visible width is unchanged.
|
|
77
|
+
|
|
78
|
+
### Variations
|
|
79
|
+
|
|
80
|
+
- **Single-pixel tick:** Change XSCROLL by 1 each frame for 1 px/frame.
|
|
81
|
+
- **Multi-pixel skip:** Advance XSCROLL by 2 or 3 per frame for faster
|
|
82
|
+
scrolling; just handle the boundary rollover at the correct modulus.
|
|
83
|
+
- **Bidirectional toggle:** Store scroll direction in a flag and negate
|
|
84
|
+
the increment to reverse at runtime.
|
|
85
|
+
|
|
86
|
+
### Cycle budget
|
|
87
|
+
|
|
88
|
+
Writing $D016 costs 6 cycles (LDA #imm 2 + STA abs 4; an earlier version
|
|
89
|
+
said 4). Preserving CSEL, MCM and RES from a shadow byte — LDA shadow /
|
|
90
|
+
AND #$F8 / ORA new / STA $D016 — is 11-14 cycles depending on whether the
|
|
91
|
+
shadow and new value are immediate, zero-page or absolute (cycle counts
|
|
92
|
+
from `docs/hardware/6510-cpu-reference.md`). This technique has no
|
|
93
|
+
raster-critical timing requirement.
|
|
94
|
+
|
|
95
|
+
The Cost line's 74,041 cycles is not the register write. It is the carry
|
|
96
|
+
frame of `recipes/oscar64/soft-scroll-h.md`, measured there in VICE: that
|
|
97
|
+
recipe shifts all 25 rows of screen and colour RAM with `memmove`, which
|
|
98
|
+
takes 3.8 PAL frames. The figure belongs to that implementation and
|
|
99
|
+
exceeds a frame; issue #18 tracks rewriting the move to fit the vertical
|
|
100
|
+
blank.
|
|
101
|
+
|
|
102
|
+
### Recipes
|
|
103
|
+
|
|
104
|
+
- `recipes/oscar64/soft-scroll-h.md`
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## soft_scroll_v — Hardware vertical soft-scroll
|
|
109
|
+
|
|
110
|
+
**Complexity:** low
|
|
111
|
+
**Region:** both
|
|
112
|
+
**Uses registers:** D011
|
|
113
|
+
**Uses kernal:** (none)
|
|
114
|
+
|
|
115
|
+
### Why
|
|
116
|
+
|
|
117
|
+
Vertical soft-scroll is the companion to horizontal: shift the entire
|
|
118
|
+
displayed raster up or down by up to 7 pixels without rewriting screen
|
|
119
|
+
RAM. Commonly used for vertical credits, FLD split-screen effects, and
|
|
120
|
+
as one axis of a two-dimensional smooth-scroller.
|
|
121
|
+
|
|
122
|
+
### How
|
|
123
|
+
|
|
124
|
+
$D011 bits 2-0 (YSCROLL) hold a three-bit fine-scroll offset. Each frame,
|
|
125
|
+
write the new YSCROLL value into $D011, preserving the DEN, RSEL, RST8,
|
|
126
|
+
ECM, and BMM bits. The visible window shifts by the specified number of
|
|
127
|
+
raster lines.
|
|
128
|
+
|
|
129
|
+
The default YSCROLL value after KERNAL initialisation is 3. Decrementing
|
|
130
|
+
YSCROLL moves the display upward; incrementing moves it downward (the
|
|
131
|
+
reference point is the top of the character row, so larger values push
|
|
132
|
+
content down).
|
|
133
|
+
|
|
134
|
+
When YSCROLL reaches 0 and a further upward shift is needed, reset
|
|
135
|
+
YSCROLL to 7 and shift the screen-RAM rows one row upward (see
|
|
136
|
+
`char_scroll_buffer_v`). For downward scrolling, the symmetrical
|
|
137
|
+
procedure applies.
|
|
138
|
+
|
|
139
|
+
### Why it works
|
|
140
|
+
|
|
141
|
+
The VIC-II's vertical display window start is fixed relative to the
|
|
142
|
+
badline condition. A badline fires when the bottom three bits of the
|
|
143
|
+
current raster line match YSCROLL (and the line is within the active
|
|
144
|
+
region). By decrementing YSCROLL, you retard the point at which the chip
|
|
145
|
+
believes the first row of each character tile begins, which slides the
|
|
146
|
+
displayed image upward by one raster line. The effect is a pure hardware
|
|
147
|
+
shift with no pixel-by-pixel CPU work.
|
|
148
|
+
|
|
149
|
+
**Critical side effect:** changing YSCROLL shifts the entire set of
|
|
150
|
+
badlines for the current frame. Because a badline costs the CPU 40 to 43
|
|
151
|
+
of the line's 63 cycles (the VIC holds the bus for cycles 15-54, and BA
|
|
152
|
+
drops three cycles earlier at cycle 12, where the CPU halts on its first
|
|
153
|
+
read; ordinary code therefore keeps only 20 cycles on a badline — an
|
|
154
|
+
earlier version said "up to 40"), smooth-scrolling code that changes YSCROLL must
|
|
155
|
+
ensure any time-sensitive raster IRQ code is written to tolerate the
|
|
156
|
+
resulting change in badline positions. This is most relevant when
|
|
157
|
+
combining vertical scroll with raster split bars.
|
|
158
|
+
|
|
159
|
+
### Variations
|
|
160
|
+
|
|
161
|
+
- **FLD (Flexible Line Distance):** Change YSCROLL mid-frame inside a
|
|
162
|
+
raster IRQ to open or close extra blank lines between rows, stretching
|
|
163
|
+
the picture vertically. This technique is built on top of soft_scroll_v.
|
|
164
|
+
- **Row hold:** Set YSCROLL to 0 and hold it to keep the display
|
|
165
|
+
"bottom-aligned" within each character row, which shifts the apparent
|
|
166
|
+
top of the screen upward 3 pixels from the KERNAL default.
|
|
167
|
+
|
|
168
|
+
### Cycle budget
|
|
169
|
+
|
|
170
|
+
Writing $D011 requires a careful read-modify-write to preserve the mode
|
|
171
|
+
bits. The safest pattern is: LDA yscroll_shadow, AND #$F8, ORA new_yscroll,
|
|
172
|
+
STA $D011 — 12-14 cycles (3 + 2 + 3 + 4 with zero-page operands, 14 with
|
|
173
|
+
absolute ones; an earlier version said 10-12). Because changing YSCROLL inside a visible
|
|
174
|
+
raster can produce glitches, the write should happen during the vertical
|
|
175
|
+
blank or in a stable raster window above line $30.
|
|
176
|
+
|
|
177
|
+
### Recipes
|
|
178
|
+
|
|
179
|
+
- `recipes/kickassembler/scroll-panel-split.md` scrolls a playfield vertically through all eight YSCROLL phases above a fixed panel; `recipes/oscar64/soft-scroll-h.md` is the horizontal counterpart.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## char_scroll_buffer_h — Char-mode horizontal scroll with screen-RAM buffer rotation
|
|
184
|
+
|
|
185
|
+
**Complexity:** medium
|
|
186
|
+
**Region:** both
|
|
187
|
+
**Uses registers:** D016
|
|
188
|
+
**Uses kernal:** (none)
|
|
189
|
+
|
|
190
|
+
### Why
|
|
191
|
+
|
|
192
|
+
The hardware XSCROLL register only provides a 0-7 pixel range. Scrolling
|
|
193
|
+
a character-mode display continuously requires advancing the content by
|
|
194
|
+
one full character column (8 pixels) at the moment XSCROLL would overflow.
|
|
195
|
+
This technique handles that overflow by rotating screen RAM and color RAM
|
|
196
|
+
in memory, effectively carrying new character data onto the visible edge
|
|
197
|
+
while resetting XSCROLL to maintain seamless motion.
|
|
198
|
+
|
|
199
|
+
### How
|
|
200
|
+
|
|
201
|
+
The display is backed by a 40×25 text screen (1000 bytes) and a parallel
|
|
202
|
+
40×25 color RAM at $D800 (1000 nibbles). To scroll left by one character
|
|
203
|
+
column:
|
|
204
|
+
|
|
205
|
+
1. Copy columns 1-39 of each row to columns 0-38 (memmove of 39 bytes
|
|
206
|
+
per row, or equivalently shift the entire 1000-byte screen window left
|
|
207
|
+
by one byte taking care at the row boundary).
|
|
208
|
+
2. Write fresh data into column 39 (the new rightmost column) from an
|
|
209
|
+
off-screen content buffer.
|
|
210
|
+
3. Repeat the same move on color RAM at $D800.
|
|
211
|
+
4. Reset XSCROLL to 7 in $D016 (it has just wrapped from 0; the column
|
|
212
|
+
move cancels the 8-pixel snap). An earlier version of this step said
|
|
213
|
+
"reset to 0", which produces an 8-pixel jump every eighth frame.
|
|
214
|
+
|
|
215
|
+
The net result: the visible content has shifted one full character to the
|
|
216
|
+
left, and XSCROLL is back at 7 ready for the next seven single-pixel steps
|
|
217
|
+
down to 0.
|
|
218
|
+
|
|
219
|
+
For scrolling right, mirror the process: copy columns 0-38 to columns
|
|
220
|
+
1-39, write fresh data into column 0, reset XSCROLL to 0 (it has just
|
|
221
|
+
wrapped from 7). This matches the `if (xscroll == 0) { shift; xscroll = 7; }`
|
|
222
|
+
form in `recipes/oscar64/soft-scroll-h.md`.
|
|
223
|
+
|
|
224
|
+
### Why it works
|
|
225
|
+
|
|
226
|
+
The hardware XSCROLL shift and the software screen-RAM shift are
|
|
227
|
+
complementary. The hardware provides fractional (sub-character) precision;
|
|
228
|
+
the software provides whole-character carries. When the two are reset
|
|
229
|
+
atomically in the same frame, the viewer sees a seamless stream of 1-pixel
|
|
230
|
+
steps even though the underlying mechanism alternates between a hardware
|
|
231
|
+
shift and a memory copy.
|
|
232
|
+
|
|
233
|
+
### Variations
|
|
234
|
+
|
|
235
|
+
- **Double-buffered screen RAM:** Maintain two screen-RAM pages and
|
|
236
|
+
alternate which one $D018 points to, avoiding tearing on fast machines.
|
|
237
|
+
- **Unrolled move:** On stock C64 there is no DMA. Unrolling the copy
|
|
238
|
+
into straight LDA abs / STA abs pairs brings it to 8 cycles per byte
|
|
239
|
+
(8,000 cycles for 40×25), which still exceeds the off-screen span on
|
|
240
|
+
both PAL (~7,056 cycles) and NTSC (~4,095); unrolling reduces the cost,
|
|
241
|
+
it does not make the move fit in the blank. An earlier version of this
|
|
242
|
+
item claimed the unrolled move "can complete inside the vertical blank".
|
|
243
|
+
- **Wide content ring buffer:** Keep the source content in a ring buffer
|
|
244
|
+
wider than 40 columns. Advance the ring pointer each time a column shift
|
|
245
|
+
fires instead of precomputing content on demand.
|
|
246
|
+
|
|
247
|
+
### Cycle budget
|
|
248
|
+
|
|
249
|
+
A naive byte-by-byte shift of 1000 bytes at roughly 10 cycles per
|
|
250
|
+
byte costs ~10,000 cycles. PAL has 63 × 312 = 19,656 cycles per frame
|
|
251
|
+
minus ~25 × 43 = 1,075 badline-stolen cycles for a CPU budget of ~18,581
|
|
252
|
+
cycles per frame. The screen shift alone therefore consumes about 54% of
|
|
253
|
+
the frame budget. The color RAM shift doubles that cost to ~108%. This
|
|
254
|
+
means a brute-force shift must be overlapped across multiple frames or
|
|
255
|
+
replaced with a DEC-and-pointer approach. An unrolled inner loop using
|
|
256
|
+
indexed addressing and/or a 2-byte-per-iteration pattern roughly halves
|
|
257
|
+
the cycle count.
|
|
258
|
+
|
|
259
|
+
### Recipes
|
|
260
|
+
|
|
261
|
+
- `recipes/oscar64/soft-scroll-h.md`
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## char_scroll_buffer_v — Char-mode vertical scroll
|
|
266
|
+
|
|
267
|
+
**Complexity:** medium
|
|
268
|
+
**Region:** both
|
|
269
|
+
**Uses registers:** D011
|
|
270
|
+
**Uses kernal:** (none)
|
|
271
|
+
|
|
272
|
+
### Why
|
|
273
|
+
|
|
274
|
+
The hardware YSCROLL register provides only a 0-7 raster-line range.
|
|
275
|
+
Continuous vertical scrolling requires a software carry to advance the
|
|
276
|
+
content by one full character row (8 raster lines) when YSCROLL overflows.
|
|
277
|
+
This technique handles that carry via a screen-RAM row shift, mirroring
|
|
278
|
+
`char_scroll_buffer_h` in the vertical axis.
|
|
279
|
+
|
|
280
|
+
### How
|
|
281
|
+
|
|
282
|
+
To scroll upward by one row (content moves up, new row appears at the
|
|
283
|
+
bottom):
|
|
284
|
+
|
|
285
|
+
1. Copy rows 1-24 of screen RAM to rows 0-23. Because a row is 40 bytes,
|
|
286
|
+
this is a memmove of 40 × 24 = 960 bytes from offset 40 to offset 0.
|
|
287
|
+
2. Write a fresh 40-byte row into row 24 from the content source.
|
|
288
|
+
3. Perform the same move on color RAM at $D800.
|
|
289
|
+
4. Reset YSCROLL to 7 in $D011 (or to the value it had before it
|
|
290
|
+
decremented to -1).
|
|
291
|
+
|
|
292
|
+
For scrolling downward, copy rows 0-23 to rows 1-24, write row 0 from
|
|
293
|
+
the source, and reset YSCROLL to 0.
|
|
294
|
+
|
|
295
|
+
The row shift should be performed while the VIC is not fetching display
|
|
296
|
+
data, or spread across raster interrupts, to avoid visible tearing. The
|
|
297
|
+
row shift does not fit in the blanking period (an earlier version said it
|
|
298
|
+
did). Even fully unrolled as LDA abs / STA abs it costs 8 cycles per byte
|
|
299
|
+
— 960 × 8 = 7,680 cycles for screen RAM alone, doubled again for colour
|
|
300
|
+
RAM — against an off-screen span of only 112 lines × 63 = 7,056 cycles on
|
|
301
|
+
PAL (lines 251-311 and 0-50) and 63 × 65 = 4,095 on NTSC; the hardware
|
|
302
|
+
vertical blank proper (PAL lines 300-15) is far smaller still. Production
|
|
303
|
+
scrollers therefore spread the move across the frames between carries,
|
|
304
|
+
shift only the rows that scroll, or write the shifted copy into a second
|
|
305
|
+
screen page during the active frame and flip $D018 in the border.
|
|
306
|
+
|
|
307
|
+
### Why it works
|
|
308
|
+
|
|
309
|
+
Same principle as the horizontal case. The hardware YSCROLL field provides
|
|
310
|
+
fine alignment within the current character row. The software row shift
|
|
311
|
+
provides the coarser row-level carry. Together they produce continuous
|
|
312
|
+
pixel-level motion.
|
|
313
|
+
|
|
314
|
+
### Variations
|
|
315
|
+
|
|
316
|
+
- **FLD combined scroll:** Use soft_scroll_v during most of the frame
|
|
317
|
+
and trigger the screen-RAM row shift only when the YSCROLL carry fires.
|
|
318
|
+
FLD effects (flexible line distance) can be applied to individual rows
|
|
319
|
+
during the same IRQ pass.
|
|
320
|
+
- **Map streamer:** Instead of pre-constructing content in a buffer,
|
|
321
|
+
decode map data on the fly into the newly exposed row. This is the
|
|
322
|
+
approach used by most C64 platformers.
|
|
323
|
+
|
|
324
|
+
### Cycle budget
|
|
325
|
+
|
|
326
|
+
A 960-byte row shift at ~10 cycles per byte costs ~9,600 cycles (7,680
|
|
327
|
+
fully unrolled). Between the last display line (250, RSEL=1) and the
|
|
328
|
+
first badline of the next frame (48 + YSCROLL) there are no badlines and
|
|
329
|
+
no character/bitmap fetches: with the default YSCROLL=3 that is lines
|
|
330
|
+
251-311 and 0-50, 112 raster lines = 7,056 cycles on PAL; because this
|
|
331
|
+
technique itself drives YSCROLL through 0-7, the span guaranteed at every
|
|
332
|
+
scroll position is lines 251-311 and 0-47, 109 lines = 6,867 cycles
|
|
333
|
+
(VICE shows the first badline at 48, 51 and 55 for YSCROLL 0, 3 and 7;
|
|
334
|
+
window and badline bounds from `docs/hardware/vic-ii-reference.md`). Only
|
|
335
|
+
lines 300-15 are vertical blanking in the video sense — 28 lines, 1,764
|
|
336
|
+
cycles (`docs/hardware/pal-ntsc-reference.md`); the rest of the span is
|
|
337
|
+
visible border, which is equally free of display DMA. Enabled sprites
|
|
338
|
+
still take their DMA in these lines. An earlier version of this paragraph
|
|
339
|
+
offered a 3,780-cycle "vertical blank" of lines 300-311 + 0-47 "of which
|
|
340
|
+
many are non-badline"; no line in that span is a badline. Since the shift
|
|
341
|
+
does not fit in the off-screen span, spread it across several frames or
|
|
342
|
+
use a double-buffer scheme where row 24 is pre-populated during the next
|
|
343
|
+
frame's active display period.
|
|
344
|
+
|
|
345
|
+
### Recipes
|
|
346
|
+
|
|
347
|
+
- `recipes/kickassembler/scroll-panel-split.md` shifts the rows on the carry frame and scrolls through all eight YSCROLL phases above a fixed panel.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## scroll_panel_split — Vertically scrolled playfield over a fixed score panel
|
|
352
|
+
|
|
353
|
+
**Complexity:** medium
|
|
354
|
+
**Region:** both
|
|
355
|
+
**Uses registers:** D011, D012, D016, D018, D021
|
|
356
|
+
**Uses kernal:** (none)
|
|
357
|
+
**Requires:** soft_scroll_v
|
|
358
|
+
**Demands:** midframe_raster_irqs
|
|
359
|
+
**Cost:** irq_slots=2, lines_active=5, cycles_per_frame=413
|
|
360
|
+
**Cost basis:** measured-vice
|
|
361
|
+
**Cost measured on:** kickassembler-scroll-panel-split (two IRQs, screen on; not the carry frame)
|
|
362
|
+
**Claims:** vic_raster_irq (owns)
|
|
363
|
+
**Claims basis:** derived-listing
|
|
364
|
+
|
|
365
|
+
### Why
|
|
366
|
+
|
|
367
|
+
A game whose playfield scrolls vertically still needs a score panel that
|
|
368
|
+
stays still. The playfield's YSCROLL changes every frame; the panel's must
|
|
369
|
+
not. A raster split has to change `$D011`, and usually `$D016`, `$D018` and a
|
|
370
|
+
colour, between the playfield's last line and the panel's first. A split at
|
|
371
|
+
one fixed line with one fixed delay works at seven YSCROLL phases and breaks
|
|
372
|
+
at the eighth (pitfall `scroll_phase_breaks_panel_split`).
|
|
373
|
+
|
|
374
|
+
### How
|
|
375
|
+
|
|
376
|
+
1. Put the panel's first line on a line that is 7 mod 8, 48 + 8k + 7, and give
|
|
377
|
+
the panel YSCROLL 7. The playfield then ends on the line before, at every
|
|
378
|
+
phase.
|
|
379
|
+
2. Let the playfield scroll with `soft_scroll_v` and `char_scroll_buffer_v`.
|
|
380
|
+
The last playfield row is cut short by the panel's badline and shows
|
|
381
|
+
7 − YSCROLL lines.
|
|
382
|
+
3. Take a raster IRQ two lines before the playfield's last line. Load every
|
|
383
|
+
register value, poll `$D012` for the last line, then wait for a delay read
|
|
384
|
+
from an eight-entry table indexed by the playfield's YSCROLL.
|
|
385
|
+
4. Store the panel's `$D016`, `$D011` and `$D018` in the right border of the
|
|
386
|
+
last playfield line.
|
|
387
|
+
5. Poll for the panel's first line and let its badline stall the CPU; store
|
|
388
|
+
the panel's background colour after the stall.
|
|
389
|
+
6. Below the panel, restore the playfield's registers and write the next
|
|
390
|
+
frame's YSCROLL before line 48.
|
|
391
|
+
|
|
392
|
+
### Why it works
|
|
393
|
+
|
|
394
|
+
A character row is fetched again from its start until it has shown all eight
|
|
395
|
+
lines. A playfield row cut short by the panel's badline therefore does not
|
|
396
|
+
count, and the panel's first row is the screen row after the last complete
|
|
397
|
+
playfield row. With the panel's first badline on 48 + 8k + 7, exactly k
|
|
398
|
+
playfield rows complete at every YSCROLL, so the panel always reads the same
|
|
399
|
+
screen row. Measured in VICE x64sc 3.10 (`recipes/kickassembler/scroll-panel-split.md`):
|
|
400
|
+
with the panel on line 216 at YSCROLL 0 instead, the panel read screen row 21
|
|
401
|
+
at playfield YSCROLL 0 and row 20 at YSCROLL 3. The cut-short playfield row
|
|
402
|
+
and the panel's first row are the same screen row, so the panel needs its own
|
|
403
|
+
screen matrix, selected through `$D018`. They also share one colour RAM row.
|
|
404
|
+
|
|
405
|
+
The split line L is a badline at exactly one playfield phase, YSCROLL = L mod
|
|
406
|
+
8. At that phase the VIC holds the CPU from cycle 12 to cycle 54 while the
|
|
407
|
+
split code waits (`badline_cycle_loss`), and a full delay lands the stores
|
|
408
|
+
about 40 cycles late, inside the panel's first line. The table entry for that
|
|
409
|
+
phase is 0: the stall is the wait. c64gameframework's panel IRQ uses the same
|
|
410
|
+
scheme, a delay table indexed by `$D011` (source:
|
|
411
|
+
https://github.com/cadaver/c64gameframework, `raster.s` and `aligneddata.s`,
|
|
412
|
+
not run here). Its panel `$D011` value `$57` has YSCROLL 7, which matches the
|
|
413
|
+
row rule above.
|
|
414
|
+
|
|
415
|
+
The panel's first line is always a badline once the split has run. Any code
|
|
416
|
+
that reads memory across cycle 12 of that line resumes at cycle 55 at every
|
|
417
|
+
phase, so a store after it has no poll jitter. The recipe puts `$D021` there.
|
|
418
|
+
|
|
419
|
+
### Variations
|
|
420
|
+
|
|
421
|
+
- **Panel at the top:** the split sets the playfield's YSCROLL below the
|
|
422
|
+
panel instead. The row-count rule above was measured only for a panel at
|
|
423
|
+
the bottom.
|
|
424
|
+
- **Horizontal scrolling too:** the split resets XSCROLL in `$D016` and
|
|
425
|
+
usually switches 38 columns to 40, as the recipe does.
|
|
426
|
+
- **Colour RAM:** a colour-RAM shift for the playfield must not touch the
|
|
427
|
+
panel's rows; it is usually run while the beam is in the panel or the
|
|
428
|
+
border (Cadaver, https://cadaver.github.io/rants/scroll.html, not measured
|
|
429
|
+
here).
|
|
430
|
+
|
|
431
|
+
### Cycle budget
|
|
432
|
+
|
|
433
|
+
The measured write window is narrow. At YSCROLL 3 the delay loop (`dec` on an
|
|
434
|
+
absolute counter and `bpl`, 9 cycles a pass) is clean at 4 and 5 passes on
|
|
435
|
+
PAL and NTSC; 3 writes `$D016` before line 214's right border and 6 misses the
|
|
436
|
+
start of line 215. Two consecutive pass counts are clean, so the margin is
|
|
437
|
+
between one and three passes (9-27 cycles) beyond the poll's 0-6 cycle jitter
|
|
438
|
+
(arithmetic; the poll loop is `cmp` absolute 4 + `bcs` taken 3 = 7 cycles). The delay
|
|
439
|
+
counter is at an absolute address (`DEC` opcode `$CE` in the assembled PRG),
|
|
440
|
+
so a pass is 9 cycles.
|
|
441
|
+
|
|
442
|
+
Two IRQs a frame: the split and the one below the panel. Measured in VICE
|
|
443
|
+
x64sc 3.10 with CIA 2 timer A free-running, read at entry and exit of both
|
|
444
|
+
handlers in a separate build of the recipe, 32 consecutive frames on PAL and
|
|
445
|
+
NTSC, from the IRQ sequence to the end of `rti`: the split takes 267-310
|
|
446
|
+
cycles depending on the phase (267 at YSCROLL 4, when line 212 is a badline)
|
|
447
|
+
and spans five raster lines; the bottom handler takes 103. That is at most 413
|
|
448
|
+
cycles a frame, the same on both models. On the one frame in eight that
|
|
449
|
+
carries, the bottom handler also shifts twenty rows (20 × 560 cycles,
|
|
450
|
+
arithmetic) and took 13,262 cycles on PAL, 13,519 on NTSC; that cost belongs
|
|
451
|
+
to `char_scroll_buffer_v`, not to the split.
|
|
452
|
+
|
|
453
|
+
### Recipes
|
|
454
|
+
|
|
455
|
+
- `recipes/kickassembler/scroll-panel-split.md`: playfield scrolling up through all eight phases over a five-row panel, with the per-phase naive-versus-table measurement on PAL and NTSC.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## infinite_scroll_h — Combine soft + buffer for continuous horizontal scroll
|
|
460
|
+
|
|
461
|
+
**Complexity:** medium
|
|
462
|
+
**Region:** both
|
|
463
|
+
**Uses registers:** D016
|
|
464
|
+
**Uses kernal:** (none)
|
|
465
|
+
**Requires:** soft_scroll_h, char_scroll_buffer_h
|
|
466
|
+
|
|
467
|
+
### Why
|
|
468
|
+
|
|
469
|
+
`soft_scroll_h` alone stops after 7 pixels. `char_scroll_buffer_h` alone
|
|
470
|
+
produces only character-column-resolution jumps. Combining both into a
|
|
471
|
+
unified scroll engine yields genuinely seamless 1-pixel-per-frame
|
|
472
|
+
continuous horizontal scrolling, which is the backbone of virtually every
|
|
473
|
+
C64 side-scroller and horizontal text scroller.
|
|
474
|
+
|
|
475
|
+
### How
|
|
476
|
+
|
|
477
|
+
Maintain two state variables: a pixel-level scroll counter (0-7) and a
|
|
478
|
+
character-level content pointer into an off-screen map buffer.
|
|
479
|
+
|
|
480
|
+
Each frame, inside a vertical-blank or raster IRQ:
|
|
481
|
+
|
|
482
|
+
1. Decrement (or increment) the pixel counter.
|
|
483
|
+
2. Write the pixel counter into XSCROLL in $D016.
|
|
484
|
+
3. If the pixel counter has rolled over (crossed 0 for leftward scroll,
|
|
485
|
+
or crossed 7 for rightward):
|
|
486
|
+
a. Shift screen RAM one column in the scroll direction.
|
|
487
|
+
b. Populate the newly exposed column from the map buffer at the
|
|
488
|
+
current content pointer.
|
|
489
|
+
c. Advance the content pointer by one column.
|
|
490
|
+
d. Advance the color RAM column the same way.
|
|
491
|
+
4. Increment the frame counter for timing.
|
|
492
|
+
|
|
493
|
+
The pixel counter and the screen-RAM shift are always in sync. The viewer
|
|
494
|
+
sees a smooth stream of 1-pixel advances — the hardware register handles
|
|
495
|
+
sub-character motion, the software carry handles character-boundary
|
|
496
|
+
transitions.
|
|
497
|
+
|
|
498
|
+
### Why it works
|
|
499
|
+
|
|
500
|
+
The VIC-II draws each frame from the fixed content of screen RAM, offset
|
|
501
|
+
by XSCROLL. Changing XSCROLL by 1 between frames produces a 1-pixel shift.
|
|
502
|
+
When XSCROLL wraps, the 1-pixel offset becomes 0 (aligned to the grid
|
|
503
|
+
again). Without the screen-RAM shift, the content would snap back 8 pixels
|
|
504
|
+
at the wrap. With the shift, the screen RAM has already advanced by one
|
|
505
|
+
column, exactly canceling the wrap reset. The visual result is a perfectly
|
|
506
|
+
continuous sub-pixel stream.
|
|
507
|
+
|
|
508
|
+
### Variations
|
|
509
|
+
|
|
510
|
+
- **Variable speed:** Increment XSCROLL by more than 1 per frame (2, 3,
|
|
511
|
+
or 4) to double, triple, or quadruple scroll speed. At 8 px/frame the
|
|
512
|
+
software and hardware components become decoupled — just shift the screen
|
|
513
|
+
each frame and skip the fractional register entirely.
|
|
514
|
+
- **Reversed direction:** All the same logic applies rightward; the only
|
|
515
|
+
difference is the direction of the XSCROLL ramp (0→7 instead of 7→0)
|
|
516
|
+
and the direction of the screen-RAM column copy.
|
|
517
|
+
- **Speed modulation:** For smooth acceleration and deceleration
|
|
518
|
+
(ease-in / ease-out), store the current speed as a fixed-point number
|
|
519
|
+
and accumulate it into the pixel counter, firing a carry whenever it
|
|
520
|
+
crosses a character boundary.
|
|
521
|
+
|
|
522
|
+
### Cycle budget
|
|
523
|
+
|
|
524
|
+
The per-frame cost is dominated by the occasional screen-RAM column shift,
|
|
525
|
+
which fires once every 8 frames at 1 px/frame. Amortized over 8 frames
|
|
526
|
+
on PAL (50 Hz), the average cost per frame is approximately 1,000 / 8 ×
|
|
527
|
+
10 cycles = ~1,250 cycles amortized from screen copy, plus ~10 cycles per
|
|
528
|
+
frame for the XSCROLL write. This is comfortably within budget for a
|
|
529
|
+
game that can afford ~18,000 CPU cycles per frame.
|
|
530
|
+
|
|
531
|
+
### Recipes
|
|
532
|
+
|
|
533
|
+
- `recipes/oscar64/soft-scroll-h.md`
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## parallax_dual_layer — Char + sprite layered scroll at different speeds
|
|
538
|
+
|
|
539
|
+
**Complexity:** high
|
|
540
|
+
**Region:** both
|
|
541
|
+
**Uses registers:** D016, D000-D00F, D010
|
|
542
|
+
**Uses kernal:** (none)
|
|
543
|
+
**Requires:** infinite_scroll_h
|
|
544
|
+
|
|
545
|
+
### Why
|
|
546
|
+
|
|
547
|
+
A single-plane scroller looks flat. Parallax — different screen layers
|
|
548
|
+
moving at different speeds — creates a strong illusion of depth. The C64
|
|
549
|
+
achieves this by running two conceptually separate scroll systems in the
|
|
550
|
+
same frame: the character-mode background scrolls at one rate, while
|
|
551
|
+
sprite-rendered foreground objects move at a different (usually faster)
|
|
552
|
+
rate. The viewer perceives the foreground as being closer because it moves
|
|
553
|
+
more quickly across the visual field.
|
|
554
|
+
|
|
555
|
+
### How
|
|
556
|
+
|
|
557
|
+
Divide the scene into two logical layers.
|
|
558
|
+
|
|
559
|
+
**Background layer:** the character-mode screen scrolled via `infinite_scroll_h`
|
|
560
|
+
(or the vertical equivalent). Each frame, advance XSCROLL by the background
|
|
561
|
+
speed and carry to the screen-RAM column shift when needed.
|
|
562
|
+
|
|
563
|
+
**Foreground layer:** hardware sprites. Each frame, update each sprite's
|
|
564
|
+
X position by the foreground speed. For a leftward-scrolling foreground
|
|
565
|
+
faster than the background, decrement each sprite's X position by more
|
|
566
|
+
pixels per frame than the background moves (sprite X grows to the right;
|
|
567
|
+
an earlier version said "increment"). When a sprite's X coordinate drops
|
|
568
|
+
below the left edge, wrap it to the right edge and update its content
|
|
569
|
+
pointer to the next foreground object.
|
|
570
|
+
|
|
571
|
+
Sprite X positions are set via $D000 (sprite 0 X low byte), $D002, $D004,
|
|
572
|
+
$D006, $D008, $D00A, $D00C, $D00E for sprites 0-7 respectively. Because
|
|
573
|
+
the X coordinate is 9 bits, the MSB for all eight sprites is packed into
|
|
574
|
+
$D010 (MSIGX), one bit per sprite. When a sprite's X coordinate exceeds
|
|
575
|
+
255, set its bit in $D010; when it drops below 256, clear it.
|
|
576
|
+
|
|
577
|
+
Typical speed ratios are 2:1 (foreground at 2 px/frame, background at 1
|
|
578
|
+
px/frame) or 4:1. The larger the ratio, the stronger the depth cue.
|
|
579
|
+
|
|
580
|
+
### Why it works
|
|
581
|
+
|
|
582
|
+
The VIC-II renders sprites on top of (or behind, if the sprite priority
|
|
583
|
+
bit is clear) the character background in the same frame. Because the chip
|
|
584
|
+
reads sprite data independently of character data, there is no coupling
|
|
585
|
+
between the two position systems. The background XSCROLL affects only the
|
|
586
|
+
character rendering pipeline; sprite positions are absolute screen
|
|
587
|
+
coordinates and are unaffected by XSCROLL. Advancing them at a different
|
|
588
|
+
rate than the background produces the parallax effect entirely at the
|
|
589
|
+
hardware level.
|
|
590
|
+
|
|
591
|
+
### Variations
|
|
592
|
+
|
|
593
|
+
- **Three-layer parallax:** Add a second character plane using a split-screen
|
|
594
|
+
raster IRQ — the top half of the screen uses one $D018 character base,
|
|
595
|
+
the bottom half uses another, with independent scroll variables for
|
|
596
|
+
each half.
|
|
597
|
+
- **Sprite foreground at 4x speed:** At 4 px/frame a foreground object
|
|
598
|
+
crosses the 320-px screen in 80 frames — 1.6 s on PAL — against 6.4 s
|
|
599
|
+
for a 1 px/frame background, a 4:1 depth cue that convincingly simulates
|
|
600
|
+
fast-moving near objects (bullets, sparks, foreground pillars). An
|
|
601
|
+
earlier version claimed "25 full-screen traversals per second", which
|
|
602
|
+
would need 160 px per frame.
|
|
603
|
+
- **Sprite-multiplexed deep parallax:** Combine a sprite multiplexer
|
|
604
|
+
(`sprite_multiplex_8`, or `sprite_multiplex_24` for larger counts — see
|
|
605
|
+
`docs/techniques/sprite.md`) with parallax to field more than 8 visible
|
|
606
|
+
foreground objects at different parallax depths.
|
|
607
|
+
|
|
608
|
+
### Cycle budget
|
|
609
|
+
|
|
610
|
+
Per-frame sprite update cost: for N sprites, update the X low byte, check
|
|
611
|
+
the MSB threshold, and conditionally flip the $D010 bit. Roughly
|
|
612
|
+
16 cycles per sprite for the conditional MSB path. Eight sprites: ~128
|
|
613
|
+
cycles. Background scroll update: ~10 cycles for the XSCROLL write plus
|
|
614
|
+
the amortized ~1,250-cycle column shift (once per 8 frames). A
|
|
615
|
+
two-layer scene at 2:1 ratio costs approximately 150 cycles per frame
|
|
616
|
+
in positional math plus the occasional carry. Well within PAL budget.
|
|
617
|
+
|
|
618
|
+
### Recipes
|
|
619
|
+
|
|
620
|
+
- No recipe yet for parallax layers.
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
## charset_parallax — Parallax inside the character layer by rolling reserved glyphs
|
|
625
|
+
|
|
626
|
+
**Complexity:** medium
|
|
627
|
+
**Region:** both
|
|
628
|
+
**Uses registers:** D018
|
|
629
|
+
**Uses kernal:** (none)
|
|
630
|
+
**Requires:** infinite_scroll_h
|
|
631
|
+
**Cost:** cycles_per_frame=378, bytes_code=26
|
|
632
|
+
**Cost basis:** derived-listing
|
|
633
|
+
**Cost measured on:** oscar64-charset-parallax (roll frame, every second frame, in the vertical blank)
|
|
634
|
+
|
|
635
|
+
### Why
|
|
636
|
+
|
|
637
|
+
`parallax_dual_layer` gets its second layer from sprites, and a raster
|
|
638
|
+
band split gets it from a second scroll value per band, so its layers
|
|
639
|
+
cannot overlap. Character parallax needs neither. The background is a
|
|
640
|
+
repeating pattern drawn with a few reserved glyphs, and the glyph bytes
|
|
641
|
+
are shifted so the pattern moves at a different speed from the
|
|
642
|
+
foreground tiles that share the screen. It costs a few hundred cycles a
|
|
643
|
+
frame, no sprites and no raster interrupt.
|
|
644
|
+
|
|
645
|
+
### How
|
|
646
|
+
|
|
647
|
+
1. **Reserve the background glyphs.** Pick a tile of glyphs, 2x2 is
|
|
648
|
+
usual (A B over C D, a 16x16 pattern), in a RAM charset. Fill every
|
|
649
|
+
background cell with A, B, C or D by world column and row parity:
|
|
650
|
+
`code = base + (world_col & 1) + 2 * (row & 1)`. Foreground tiles use
|
|
651
|
+
other codes, never these four.
|
|
652
|
+
2. **Scroll the whole screen as the foreground.** `$D016` XSCROLL each
|
|
653
|
+
frame and a one-column shift of screen RAM on the carry
|
|
654
|
+
(`infinite_scroll_h`). Background cells ride along with it, so
|
|
655
|
+
without step 3 the pattern moves at the foreground's speed.
|
|
656
|
+
3. **Shift the glyph bytes against the scroll.** Each pixel row of the
|
|
657
|
+
tile is a 16-bit word, left glyph's byte high. One pixel right is
|
|
658
|
+
`LDA right,X / LSR / ROR left,X / ROR right,X`: LSR drops the right
|
|
659
|
+
byte's last pixel into carry, the first ROR takes it in at the left
|
|
660
|
+
edge and drops the left byte's last pixel into carry, the second ROR
|
|
661
|
+
takes that in. That is 20 cycles a pixel row, 320 for the 16 rows of
|
|
662
|
+
a 2x2 tile. One pixel left is the mirror: `LDA left,X / ASL / ROL
|
|
663
|
+
right,X / ROL left,X`.
|
|
664
|
+
4. **Pick the rate.** The pattern's speed on screen is the foreground's
|
|
665
|
+
speed minus the glyph shift speed. A foreground at 1 px a frame
|
|
666
|
+
leftward with a one-pixel right roll every second frame puts the
|
|
667
|
+
background at 1/2 px a frame leftward. A roll every frame holds it
|
|
668
|
+
still; a roll in the same direction as the scroll makes it faster
|
|
669
|
+
than the foreground.
|
|
670
|
+
5. **Do it in the blank.** The glyph bytes are read on every raster
|
|
671
|
+
line of every background row, so the roll must finish before the
|
|
672
|
+
first background row is fetched, or that frame shows old rows above
|
|
673
|
+
the write and new rows below it.
|
|
674
|
+
|
|
675
|
+
Vertical is byte rotation, not bit rotation: to move the tile down one
|
|
676
|
+
pixel, each 16-byte column (A over C, B over D) moves every byte to the
|
|
677
|
+
next row's address, and the last row's byte wraps to the first. Unrolled, that
|
|
678
|
+
is a load and a store per byte, about 128 cycles a column (arithmetic
|
|
679
|
+
from 4-cycle absolute loads and stores, not built here).
|
|
680
|
+
|
|
681
|
+
### Why it works
|
|
682
|
+
|
|
683
|
+
The VIC-II has no copy of a glyph. It reads eight bytes from the
|
|
684
|
+
charset for each cell's code on every line of the row, so changing the
|
|
685
|
+
tile's 32 bytes changes every background cell on the screen at once,
|
|
686
|
+
however many there are. The screen RAM is untouched; the scroll code
|
|
687
|
+
still moves the cells, and the cell grid and the glyph content add. In
|
|
688
|
+
the recipe the foreground moved 203 px in 203 frames and the pattern
|
|
689
|
+
101 px against it, so the pattern moved 102 px, which the exit
|
|
690
|
+
screenshot confirms to the pixel.
|
|
691
|
+
|
|
692
|
+
### Variations
|
|
693
|
+
|
|
694
|
+
- **Pre-shifted copies instead of a roll.** Keep every phase of the
|
|
695
|
+
tile in a table (16 phases of 32 bytes for a 2x2 hires tile, 512
|
|
696
|
+
bytes) and copy this frame's phase in. The copy does not accumulate
|
|
697
|
+
error and can jump phases, but in the recipe it cost 560 cycles,
|
|
698
|
+
including the pointer set-up for the phase, against 378 for the roll.
|
|
699
|
+
The roll is cheaper because it touches each byte in place.
|
|
700
|
+
- **One charset per phase.** Store each phase in its own charset and
|
|
701
|
+
flip `$D018` (Hawkeye used four charsets switched continuously, per
|
|
702
|
+
C64-Wiki; not measured here). One store a frame, 2 KB of RAM per
|
|
703
|
+
phase, and every other glyph copied into every charset.
|
|
704
|
+
- **Multicolour.** A multicolour pixel is two bits, so one pixel step is
|
|
705
|
+
two rolls, twice the cycles (codebase64; not built here).
|
|
706
|
+
- **Shared cells.** Where a foreground tile and the background share a
|
|
707
|
+
cell, that cell needs its own glyph, rebuilt after each roll as
|
|
708
|
+
`(background AND NOT mask) OR foreground` per byte, the merge
|
|
709
|
+
`char_bullets` does for bullets. The recipe forbids shared cells
|
|
710
|
+
instead: foreground tiles are whole cells, so a tile's edge is a cell
|
|
711
|
+
edge.
|
|
712
|
+
- **Animated glyphs.** Rewriting a glyph's bytes in place is
|
|
713
|
+
`charset_animation`; this technique is that method with the new bytes
|
|
714
|
+
computed from the old ones by a shift.
|
|
715
|
+
|
|
716
|
+
### Cycle budget
|
|
717
|
+
|
|
718
|
+
The roll of a 2x2 hires tile is 378 cycles measured with CIA1 timer B,
|
|
719
|
+
which includes about five cycles of the timer's stop; the instruction
|
|
720
|
+
count is 373 with `JSR` and `RTS`. Measured in VICE x64sc 3.10 on PAL and NTSC alike,
|
|
721
|
+
because it runs in the vertical blank where no cycles are stolen. It
|
|
722
|
+
runs every second frame at half speed, so the typical frame is 378 or 0.
|
|
723
|
+
The worst frame is a roll frame; the `**Cost:**` line states it, and
|
|
724
|
+
the 26-byte routine from the Oscar64 map. A larger tile scales the roll
|
|
725
|
+
linearly: 20 cycles per pixel row per glyph pair. The recipe's screen
|
|
726
|
+
shift on the carry frame, 12,321 cycles on PAL and 12,537 on NTSC, is
|
|
727
|
+
`infinite_scroll_h`'s cost, not this technique's.
|
|
728
|
+
|
|
729
|
+
### Recipes
|
|
730
|
+
|
|
731
|
+
- `recipes/oscar64/charset-parallax.md` — a 2x2 background tile rolled
|
|
732
|
+
one pixel every second frame under a foreground scrolled at 1 px a
|
|
733
|
+
frame; the program checks the glyph bytes against a model and every
|
|
734
|
+
cell against the map after 203 frames, and prints both methods'
|
|
735
|
+
cycles; PAL and NTSC
|
|
736
|
+
|
|
737
|
+
### Sources
|
|
738
|
+
|
|
739
|
+
- https://codebase.c64.org/doku.php?id=base:simple_parallax_shifting
|
|
740
|
+
(2x2 tile, ASL/ROL and LSR/ROR across the glyph pair, byte moves for
|
|
741
|
+
vertical, a call every second frame for a slower layer, twice per
|
|
742
|
+
frame in multicolour). Read for facts; no code is taken from it.
|
|
743
|
+
- https://www.c64-wiki.com/wiki/Parallax_Scrolling (bits "rolled
|
|
744
|
+
(horizontally) or copied (all directions) within one or several
|
|
745
|
+
chars"; X-Out, Snare and Parallax named; Hawkeye's four charsets;
|
|
746
|
+
raster-band layers "cannot overlap"). Not measured here.
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## bitmap_scroll — Bitmap-mode scroll via $D016 + bitmap shuffling
|
|
751
|
+
|
|
752
|
+
**Complexity:** high
|
|
753
|
+
**Region:** both
|
|
754
|
+
**Uses registers:** D011, D016, D018
|
|
755
|
+
**Uses kernal:** (none)
|
|
756
|
+
|
|
757
|
+
### Why
|
|
758
|
+
|
|
759
|
+
Bitmap mode gives pixel-level control over every dot on the screen —
|
|
760
|
+
320×200 in standard single-color mode, 160×200 in multicolor mode. Some
|
|
761
|
+
demos and games want to scroll a pixel-accurate drawn scene rather than
|
|
762
|
+
a character-based one. This is fundamentally harder than character-mode
|
|
763
|
+
scrolling because there is no sparse screen-RAM grid to rotate; the entire
|
|
764
|
+
8000-byte bitmap must be shifted, or an expensive double-buffer strategy
|
|
765
|
+
must be used.
|
|
766
|
+
|
|
767
|
+
### How
|
|
768
|
+
|
|
769
|
+
There are two viable approaches. Both use $D016 XSCROLL for fine-scroll
|
|
770
|
+
and $D011 YSCROLL for fine vertical scroll, exactly as in character mode.
|
|
771
|
+
The difference is how the coarser carry is handled.
|
|
772
|
+
|
|
773
|
+
**Approach 1 — memshift per frame.**
|
|
774
|
+
|
|
775
|
+
When the hardware XSCROLL register wraps (after 8 sub-pixel steps), shift
|
|
776
|
+
the entire 8000-byte bitmap left by one cell. The standard bitmap is 8000
|
|
777
|
+
bytes laid out as 25 bands (one per character row) × 40 cells × 8 bytes;
|
|
778
|
+
the 8 bytes of a cell are its 8 scanlines, so cell (col,row) starts at
|
|
779
|
+
(row*40+col)*8 and the cell to its right is 8 bytes further on (layout
|
|
780
|
+
from `docs/techniques/bitmap-modes.md`; an earlier version of this
|
|
781
|
+
paragraph gave "40 columns × 200 rows × 8 bytes", which is 64,000 bytes,
|
|
782
|
+
and "one byte per 8-row band"). Shifting the picture left by one cell
|
|
783
|
+
therefore moves every 8-byte cell to the cell before it — a memmove of the
|
|
784
|
+
whole 8000-byte bitmap down by 8 bytes — after which the last cell of each
|
|
785
|
+
of the 25 bands (40 × 8 = 320 bytes per band) is redrawn from source; the
|
|
786
|
+
1000-byte screen RAM (and Color RAM in multicolor mode) shifts by one byte
|
|
787
|
+
in step.
|
|
788
|
+
|
|
789
|
+
For vertical scrolling by one character row, the coarse carry is one
|
|
790
|
+
band: move the bitmap up by 320 bytes (40 cells × 8 bytes = one row of
|
|
791
|
+
characters, 8 raster lines) — a 7,680-byte memmove — and refill the
|
|
792
|
+
bottom 320 bytes. An earlier version said "40 bytes", which shifts by
|
|
793
|
+
five cells horizontally within the same band, not by one row. The
|
|
794
|
+
screen-RAM attribute matrix (1000 bytes) moves by 40 bytes at the same
|
|
795
|
+
time, and in multicolor bitmap mode colour RAM at $D800 moves by 40 bytes
|
|
796
|
+
as well. Note that a vertical shift of fewer than 8 lines is not a
|
|
797
|
+
memmove at all — each scanline byte moves within its own cell, and line 0
|
|
798
|
+
of a cell takes line 7 of the cell in the band above — which is why
|
|
799
|
+
bitmap scrollers carry vertically by whole bands and use YSCROLL for the
|
|
800
|
+
intermediate lines.
|
|
801
|
+
|
|
802
|
+
Cycle cost: an 8000-byte shift at ~10 cycles per byte = ~80,000 cycles.
|
|
803
|
+
PAL provides ~18,500 CPU cycles per frame (after badlines). An 8000-byte
|
|
804
|
+
shift is approximately 4.3 frames of CPU time at full speed. This approach
|
|
805
|
+
is not viable at 50 Hz for a full-screen scrolling bitmap without
|
|
806
|
+
significant compromises.
|
|
807
|
+
|
|
808
|
+
**Approach 2 — double-buffer with $D018 page flip.**
|
|
809
|
+
|
|
810
|
+
Maintain two 8000-byte bitmap buffers at offsets $0000 and $2000 of the
|
|
811
|
+
VIC bank. Use bank 1 ($4000-$7FFF) or bank 3 ($C000-$FFFF): in banks 0
|
|
812
|
+
and 2 the VIC sees character ROM at bank offset $1000-$1FFF (CPU $1000 /
|
|
813
|
+
$9000), which lands inside the low bitmap page
|
|
814
|
+
(`docs/hardware/vic-ii-reference.md`, character ROM shadowing). In bank 3
|
|
815
|
+
the low page spans $D000-$DFFF, which the CPU sees as I/O, so the CPU
|
|
816
|
+
must bank I/O out via $01 (e.g. $34) while writing that page. An earlier
|
|
817
|
+
version of this section named $8000, which is one of the banks that does
|
|
818
|
+
not work. Each frame, render
|
|
819
|
+
(draw and scroll) into the invisible back buffer, then toggle $D018 to
|
|
820
|
+
flip the two buffers at the start of vertical blank. This prevents tearing
|
|
821
|
+
and decouples the rendering time from the frame deadline — as long as
|
|
822
|
+
rendering completes before the vertical blank of the target frame.
|
|
823
|
+
|
|
824
|
+
$D018 bits 7-4 select the 1 KB video matrix within the VIC bank (screen
|
|
825
|
+
RAM in text mode; in bitmap mode it holds the per-cell
|
|
826
|
+
foreground/background colour nibbles). Bits 3-1 select the 2 KB character
|
|
827
|
+
generator in text mode; in bitmap mode only bit 3 matters: 0 puts the
|
|
828
|
+
8000-byte bitmap at bank offset $0000, 1 at $2000, and bits 2-1 are
|
|
829
|
+
ignored. Toggling bit 3 of $D018 therefore switches between the two
|
|
830
|
+
bitmap pages. (An earlier version of this paragraph described the video
|
|
831
|
+
matrix as "the character generator pointer in text mode" and never said
|
|
832
|
+
what it holds in bitmap mode.)
|
|
833
|
+
|
|
834
|
+
Even with double buffering, filling 8000 bytes per frame is expensive.
|
|
835
|
+
Practical scrolling bitmap scenes either reduce the scrolling area to a
|
|
836
|
+
sub-screen viewport, use hardware XSCROLL + YSCROLL to cover most frames
|
|
837
|
+
with no pixel work, and only do the 8000-byte shift once every 8 frames
|
|
838
|
+
when the coarse carry fires.
|
|
839
|
+
|
|
840
|
+
### Why it works
|
|
841
|
+
|
|
842
|
+
The VIC-II treats the bitmap as a contiguous region of video memory. XSCROLL
|
|
843
|
+
and YSCROLL shift the rendering pipeline's start position within each
|
|
844
|
+
8×8 cell exactly as in character mode. The fine-scroll mechanism is
|
|
845
|
+
identical. The cost difference is in the carry: where character mode
|
|
846
|
+
carries by rotating 1000 bytes of screen RAM, bitmap mode must carry by
|
|
847
|
+
rotating 8000 bytes of raw pixel data. The $D018 page-flip trick avoids
|
|
848
|
+
moving pixel data altogether by pointing the VIC at a different memory
|
|
849
|
+
region, leaving the shifting work to the back-buffer renderer which runs
|
|
850
|
+
across multiple cycles asynchronously.
|
|
851
|
+
|
|
852
|
+
### Variations
|
|
853
|
+
|
|
854
|
+
- **Reduced viewport:** Apply bitmap scrolling only to a horizontal strip
|
|
855
|
+
(e.g., the bottom 100 rows). Use a raster IRQ to switch $D018 or the
|
|
856
|
+
display mode at the viewport edge. This halves the bitmap size and
|
|
857
|
+
memory requirement.
|
|
858
|
+
- **Sprite overlay on bitmap:** Hardware sprites work the same way in
|
|
859
|
+
bitmap mode. Combine with `parallax_dual_layer` — sprite foreground
|
|
860
|
+
objects at a faster rate over a slow-scrolling bitmap background.
|
|
861
|
+
- **Vertical bitmap scroll only:** Shifting 8000 bytes vertically is the
|
|
862
|
+
same cost as horizontally, but the double-buffer flip approach works
|
|
863
|
+
equally well. Vertical-only bitmap scroll is somewhat more common in
|
|
864
|
+
intros because vertical motion on a bitmap preserves horizontal
|
|
865
|
+
compositional alignment better than horizontal motion.
|
|
866
|
+
|
|
867
|
+
### Cycle budget
|
|
868
|
+
|
|
869
|
+
Fine-scroll step (XSCROLL or YSCROLL write only, no carry): ~10 cycles.
|
|
870
|
+
|
|
871
|
+
Coarse carry, memshift approach (fires once per 8 frames at 1 px/frame):
|
|
872
|
+
8000 bytes × 10 cycles/byte = 80,000 cycles. Spread across 8 frames:
|
|
873
|
+
10,000 cycles/frame average, or ~54% of the PAL per-frame budget.
|
|
874
|
+
The colour carry must move in lockstep with the pixel carry: the
|
|
875
|
+
1000-byte video matrix (each byte holds the cell's foreground and
|
|
876
|
+
background nibbles), plus the 1000 nibbles at $D800 in multicolour bitmap
|
|
877
|
+
mode — a further ~10,000-20,000 cycles per coarse step, about an eighth
|
|
878
|
+
to a quarter of the bitmap shift. (An earlier version spoke of a
|
|
879
|
+
"4000-byte" colour array; there are 40 × 25 = 1000 cells and no
|
|
880
|
+
4000-entry structure anywhere in the VIC.) Combined, the overhead is
|
|
881
|
+
practical only for slow-scrolling backgrounds or sub-screen viewports.
|
|
882
|
+
|
|
883
|
+
Double-buffer approach: the page flip itself costs ~10 cycles ($D018
|
|
884
|
+
write). The rendering work (clearing and redrawing the 8000-byte back
|
|
885
|
+
buffer) is the real budget item and is scene-specific.
|
|
886
|
+
|
|
887
|
+
### Recipes
|
|
888
|
+
|
|
889
|
+
- No recipe yet for bitmap scrolling.
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
## tile_map_render — Metatile map decode to screen and colour RAM
|
|
894
|
+
|
|
895
|
+
**Complexity:** medium
|
|
896
|
+
**Region:** both
|
|
897
|
+
**Uses registers:** (none)
|
|
898
|
+
**Uses kernal:** (none)
|
|
899
|
+
**Cost:** cycles_per_frame=268
|
|
900
|
+
**Cost basis:** arithmetic
|
|
901
|
+
**Cost measured on:** oscar64-tile-map-render (one column edge, 11 metatiles)
|
|
902
|
+
|
|
903
|
+
### Why
|
|
904
|
+
|
|
905
|
+
A scrolling game level is far larger than the 1000 cells of one text
|
|
906
|
+
screen, and a level stored as raw screen codes plus colour costs two bytes
|
|
907
|
+
a cell. Storing the level as a grid of metatiles (here 2x2 characters plus
|
|
908
|
+
one colour) divides that by eight, and run-length coding the metatile
|
|
909
|
+
rows takes it down further: the recipe's 20 x 11 map is 135 stream bytes
|
|
910
|
+
for 220 metatiles, which expand to 880 screen bytes and 880 colour
|
|
911
|
+
nibbles. The decoder is also the thing that feeds `char_scroll_buffer_h`
|
|
912
|
+
and `char_scroll_buffer_v`: both say "write fresh data into column 39"
|
|
913
|
+
or "into the new row" from an off-screen source, and this technique is
|
|
914
|
+
that source.
|
|
915
|
+
|
|
916
|
+
### How
|
|
917
|
+
|
|
918
|
+
Three tables and two decoders.
|
|
919
|
+
|
|
920
|
+
1. **Metatile table.** One entry per metatile: four screen codes
|
|
921
|
+
(top-left, top-right, bottom-left, bottom-right) and one colour. A
|
|
922
|
+
per-character colour variant stores four colour bytes instead of one;
|
|
923
|
+
the write count is the same, the table is three bytes larger per
|
|
924
|
+
metatile.
|
|
925
|
+
2. **Map.** One byte per metatile, MAP_W wide by MAP_H high, decoded once
|
|
926
|
+
into RAM at level start. It is indexed as `map[my * MAP_W + mx]`. For
|
|
927
|
+
a level wider than the screen MAP_W is the level width, not 20.
|
|
928
|
+
3. **RLE row streams.** Each map row is its own stream of control bytes.
|
|
929
|
+
In the recipe's format bit 7 set means a run (the next byte repeated
|
|
930
|
+
`c & 0x7f` times), bit 7 clear means `c` literal bytes follow, and zero
|
|
931
|
+
ends the row. The decoder returns the address after the terminator, so
|
|
932
|
+
the rows are walked in sequence with no offset table.
|
|
933
|
+
4. **Row decode.** Draw the top or bottom character row of one map row
|
|
934
|
+
into one screen row: for each metatile write two screen codes and two
|
|
935
|
+
colours. This is the new-row source for `char_scroll_buffer_v` and
|
|
936
|
+
`soft_scroll_v`: a vertical scroll steps one character row at a time,
|
|
937
|
+
so it asks for half a metatile row per step and alternates `half`
|
|
938
|
+
between 0 and 1.
|
|
939
|
+
5. **Column decode.** Draw the left or right character column of one
|
|
940
|
+
map column into screen column 0 or 39, all rows: for each metatile row
|
|
941
|
+
write one screen code and one colour at `s[0]` and again at `s[40]`,
|
|
942
|
+
then step 80 bytes. This is the new-column source for
|
|
943
|
+
`char_scroll_buffer_h` and `soft_scroll_h`, and again `half` alternates
|
|
944
|
+
because a metatile is two columns wide.
|
|
945
|
+
|
|
946
|
+
The two edge decoders, as built in scratch with Oscar64 build 2026-05-19
|
|
947
|
+
at `-O2` to confirm they compile (they are not the recipe's listing and
|
|
948
|
+
their cost is not measured here):
|
|
949
|
+
|
|
950
|
+
```c
|
|
951
|
+
struct Metatile { char c[4]; char col; };
|
|
952
|
+
extern const struct Metatile tiles[];
|
|
953
|
+
extern char map[MAP_W * MAP_H]; // one byte per metatile, decoded once
|
|
954
|
+
|
|
955
|
+
// Left (half 0) or right (half 1) character column of map column mx,
|
|
956
|
+
// into screen column sx (0 or 39), every metatile row.
|
|
957
|
+
void decode_column(char mx, char half, char sx)
|
|
958
|
+
{
|
|
959
|
+
char *s = Screen + MAP_ROW * 40 + sx;
|
|
960
|
+
char *k = Color + MAP_ROW * 40 + sx;
|
|
961
|
+
const char *m = map + mx;
|
|
962
|
+
for (char y = 0; y < MAP_H; y++) {
|
|
963
|
+
const struct Metatile *t = tiles + *m;
|
|
964
|
+
s[0] = t->c[half]; s[40] = t->c[half + 2];
|
|
965
|
+
k[0] = t->col; k[40] = t->col;
|
|
966
|
+
s += 80; k += 80; m += MAP_W;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Top (half 0) or bottom (half 1) character row of map row my, starting
|
|
971
|
+
// at map column mx, into screen row sy.
|
|
972
|
+
void decode_row(char mx, char my, char half, char sy)
|
|
973
|
+
{
|
|
974
|
+
char *s = Screen + sy * 40;
|
|
975
|
+
char *k = Color + sy * 40;
|
|
976
|
+
const char *m = map + my * MAP_W + mx;
|
|
977
|
+
for (char x = 0; x < 20; x++) {
|
|
978
|
+
const struct Metatile *t = tiles + m[x];
|
|
979
|
+
s[0] = t->c[2 * half]; s[1] = t->c[2 * half + 1];
|
|
980
|
+
k[0] = t->col; k[1] = t->col;
|
|
981
|
+
s += 2; k += 2;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### Why it works
|
|
987
|
+
|
|
988
|
+
Screen RAM holds screen codes and colour RAM at `$D800` holds one nibble
|
|
989
|
+
per cell, so a metatile is nothing more than a fixed pattern of writes to
|
|
990
|
+
both. The VIC-II reads the two arrays every badline; nothing in the chip
|
|
991
|
+
knows about metatiles, which is why the decoder can write at any time
|
|
992
|
+
the cell is off-screen or about to be overwritten anyway. Keeping the map
|
|
993
|
+
as one byte per metatile in RAM, rather than decoding the RLE on demand,
|
|
994
|
+
is what makes the column decode cheap: a column of a run-length coded row
|
|
995
|
+
cannot be reached without decoding the row up to it, but an unpacked map
|
|
996
|
+
is a stride-MAP_W walk.
|
|
997
|
+
|
|
998
|
+
### Variations
|
|
999
|
+
|
|
1000
|
+
- **Per-character colour.** `char col[4]` in the metatile instead of one
|
|
1001
|
+
byte. Same write count; use it when a metatile mixes, say, a green tree
|
|
1002
|
+
top over a brown trunk.
|
|
1003
|
+
- **Larger metatiles.** 4x4 characters with a 16-byte pattern divides the
|
|
1004
|
+
map size by a further four. The edge decoders then alternate `half`
|
|
1005
|
+
over four values.
|
|
1006
|
+
- **CharPad import.** CharPad's `.ctm` (version 8) already holds
|
|
1007
|
+
characters, tiles, attributes and a map, and Oscar64's `#embed` extracts
|
|
1008
|
+
each channel directly: `ctm_chars`, `ctm_tiles8` / `ctm_tiles16`,
|
|
1009
|
+
`ctm_map8` / `ctm_map16`, `ctm_attr1` / `ctm_attr2`
|
|
1010
|
+
(`docs/toolchains/oscar64-reference.md`, "Embedding" section, the
|
|
1011
|
+
`#embed ctm_chars` paragraph; the Oscar64 manual `oscar64.md`,
|
|
1012
|
+
"Embedding sprite and graphics data", is the source). The directive
|
|
1013
|
+
must stand alone on its own line inside the initialiser braces. The
|
|
1014
|
+
`.ctm` layout is summarised in `docs/art/asset-pipelines.md` under
|
|
1015
|
+
"Charsets (.ctm from CharPad)". A CharPad tile maps onto the
|
|
1016
|
+
`Metatile` struct here as its character indices plus its attribute
|
|
1017
|
+
byte; the RLE streams are then whatever the build produces, or
|
|
1018
|
+
`#embed ... rle` for a plain run-length pass. No page in this KB ships
|
|
1019
|
+
a `.ctm`, and the recipe below needs none.
|
|
1020
|
+
- **RLE variants.** The recipe's format caps a run at 127 and has no
|
|
1021
|
+
escape for a single repeated pair. A two-byte `(count, value)` format
|
|
1022
|
+
with no literal mode is smaller code and worse on noisy rows.
|
|
1023
|
+
|
|
1024
|
+
### Cycle budget
|
|
1025
|
+
|
|
1026
|
+
Measured in VICE x64sc 3.10 with a CIA1 timer B harness, display blanked
|
|
1027
|
+
and interrupts masked, Oscar64 `-O2`, the recipe's listing (rung 1; the
|
|
1028
|
+
figures are identical on PAL and NTSC because they count CPU cycles
|
|
1029
|
+
only):
|
|
1030
|
+
|
|
1031
|
+
| Step | Cycles | Per unit |
|
|
1032
|
+
|---|---|---|
|
|
1033
|
+
| RLE decode, 11 rows, 135 stream bytes to 220 map bytes | 8,828 | 40.1 per decoded byte |
|
|
1034
|
+
| Expand 220 metatiles (880 screen + 880 colour writes) | 10,731 | 48.8 per metatile |
|
|
1035
|
+
|
|
1036
|
+
The edge decoders above were not timed. One column decode touches 11
|
|
1037
|
+
metatiles and writes four bytes for each, half of what `expand_row`
|
|
1038
|
+
writes per metatile, so a figure in the low hundreds of cycles is
|
|
1039
|
+
arithmetic from the expand figure (rung 3), not a measurement: half of
|
|
1040
|
+
48.8 is 24.4 per metatile, and eleven metatiles come to about 268
|
|
1041
|
+
cycles, which is the figure on the Cost line above. A full-map expand at 10,731
|
|
1042
|
+
cycles is about 55% of the ~19,656-cycle PAL frame, so it belongs at level
|
|
1043
|
+
start, not inside the scroll loop; the scroll loop does one edge decode
|
|
1044
|
+
per character step.
|
|
1045
|
+
|
|
1046
|
+
### Recipes
|
|
1047
|
+
|
|
1048
|
+
- `recipes/oscar64/tile-map-render.md`
|
|
1049
|
+
- `recipes/oscar64/level-rle-decoder.md` (the same RLE format on three 40 x 22 rooms: ratio, decode cycles and decoder size measured, in C and by hand)
|
|
1050
|
+
- `recipes/oscar64/tile-grid-collision.md` (tests a sprite against the decoded map array this technique fills; `tile_grid_collision` in `logic.md`)
|
|
1051
|
+
|
|
1052
|
+
---
|
|
1053
|
+
|
|
1054
|
+
## dycp_scroller — DYCP: each text column at its own pixel Y, drawn through the charset
|
|
1055
|
+
|
|
1056
|
+
**Complexity:** medium
|
|
1057
|
+
**Region:** both
|
|
1058
|
+
**Uses registers:** D012, D016, D018
|
|
1059
|
+
**Uses kernal:** (none)
|
|
1060
|
+
**Requires:** frame_sync_loop
|
|
1061
|
+
**Cost:** cycles_per_frame=5343, bytes_code=4117, bytes_data=1090, zp_bytes=2, irq_slots=1
|
|
1062
|
+
**Cost basis:** derived-listing
|
|
1063
|
+
**Cost measured on:** kickassembler-dycp-scroller (worst frame, 39 columns, in the vertical blank)
|
|
1064
|
+
|
|
1065
|
+
### Why
|
|
1066
|
+
|
|
1067
|
+
A sine scroller that moves whole characters between rows
|
|
1068
|
+
(`recipes/kickassembler/sine-scroller.md`) steps eight pixels at a time
|
|
1069
|
+
vertically. DYCP, Different Y Character Position, puts every column at
|
|
1070
|
+
its own pixel height. The trick is to stop moving characters around the
|
|
1071
|
+
screen at all. Each column of the band is a fixed vertical strip of
|
|
1072
|
+
character cells that never changes; what moves is the glyph inside the
|
|
1073
|
+
strip, copied every frame into a custom charset at the pixel row the wave
|
|
1074
|
+
gives. The VIC-II reads the charset afresh on every raster line, so a
|
|
1075
|
+
glyph written at byte offset `y` of a strip appears `y` pixels down it.
|
|
1076
|
+
|
|
1077
|
+
### How
|
|
1078
|
+
|
|
1079
|
+
1. **Lay the strips once.** Give the band N screen rows. Column `c`,
|
|
1080
|
+
cell `k` holds charset slot `base + c*N + k`. Because a slot is eight
|
|
1081
|
+
consecutive bytes, the N cells of one column are `8N` consecutive
|
|
1082
|
+
charset bytes, and pixel row `y` of the strip is the single byte
|
|
1083
|
+
`strip + y`. Set `$D018` to point at the charset and never write the
|
|
1084
|
+
band's screen RAM again. Everything outside the band shows a blank
|
|
1085
|
+
slot from the same charset, and any text elsewhere on the screen has
|
|
1086
|
+
to be drawn from slots the strips do not use.
|
|
1087
|
+
2. **Each frame, per column:** blank the eight bytes the glyph occupied
|
|
1088
|
+
last frame; look the column's new `Y` up (`sine[phase + c*STEP]`);
|
|
1089
|
+
copy the glyph's eight rows to `strip + Y .. strip + Y + 7`. Seen as
|
|
1090
|
+
cells, rows `0 .. 7 - (Y & 7)` of the glyph land in cell `Y >> 3` at
|
|
1091
|
+
row offset `Y & 7` and the rest in the cell below; the address form
|
|
1092
|
+
does that split for nothing. `Y` runs from 0 to `8N - 8`, so a strip
|
|
1093
|
+
N cells tall gives `8N - 8` pixels of travel: six cells, 40 pixels.
|
|
1094
|
+
3. **Keep the glyph source indexable.** A copy of the glyphs the message
|
|
1095
|
+
uses, 32 of them, in 256 bytes: `glyph * 8` then fits a byte and one
|
|
1096
|
+
`lda font+r,y` reaches any row of any glyph. Screen codes for space
|
|
1097
|
+
and punctuation lie above 31, so the message is remapped onto the 32
|
|
1098
|
+
slots when it is assembled or when it is fed into the ring buffer.
|
|
1099
|
+
4. **Scroll horizontally as usual.** `$D016` XSCROLL a pixel a frame and
|
|
1100
|
+
a ring-buffer shift every eighth frame (`soft_scroll_h` and
|
|
1101
|
+
`char_scroll_buffer_h` above); the shift changes which glyph a column
|
|
1102
|
+
copies, nothing else.
|
|
1103
|
+
|
|
1104
|
+
### Why it works
|
|
1105
|
+
|
|
1106
|
+
The character generator reads eight bytes per glyph at charset base plus
|
|
1107
|
+
code times eight, one byte per raster line of the row. Consecutive slots
|
|
1108
|
+
are consecutive in memory, so a column of consecutive slots is one
|
|
1109
|
+
unbroken run of bytes and a glyph can be placed at any byte offset in
|
|
1110
|
+
it. Where the old and new positions overlap, the write after the clear
|
|
1111
|
+
wins, so clearing the previous eight rows and then writing the new eight
|
|
1112
|
+
is correct for any move up to eight pixels a frame. The screen RAM never
|
|
1113
|
+
changes, so nothing is redrawn on the character grid and no part of the
|
|
1114
|
+
effect costs screen or colour writes. `$D016` still applies: the fine
|
|
1115
|
+
scroll shifts every row including any caption outside the band.
|
|
1116
|
+
|
|
1117
|
+
### Variations
|
|
1118
|
+
|
|
1119
|
+
- **Two charsets, `$D018` flip.** Copy into the charset the VIC is not
|
|
1120
|
+
showing and swap at the frame sync. Buys the freedom to run the copy
|
|
1121
|
+
inside the display at 2 KB per charset and one register write; not
|
|
1122
|
+
needed when the copy is budgeted to finish above the band, which the
|
|
1123
|
+
recipe measures.
|
|
1124
|
+
- **Cheaper clear.** When the per-frame move is at most one pixel, two
|
|
1125
|
+
zero stores (the row above and the row below the new glyph) replace
|
|
1126
|
+
the eight-store clear, six stores a column fewer. Ties the copy to the
|
|
1127
|
+
wave's speed.
|
|
1128
|
+
- **1x2 letters.** Sixteen-row glyphs in strips two cells wider apart:
|
|
1129
|
+
twice the copy per column, half the columns for the same slot budget.
|
|
1130
|
+
- **Wave tables.** Separate speed and amplitude tables indexed by frame
|
|
1131
|
+
give a wave that breathes; a second sine added to the first gives a
|
|
1132
|
+
compound wave. Amplitude is bounded by `8N - 8` less the glyph height.
|
|
1133
|
+
|
|
1134
|
+
### Cycle budget
|
|
1135
|
+
|
|
1136
|
+
Per column per frame: 16 stores and 8 loads, plus the table lookups.
|
|
1137
|
+
Unrolled with absolute-indexed addressing, the recipe's copy is 136
|
|
1138
|
+
cycles a column: 39 columns in 5,305 to 5,343 cycles over 304 PAL frames,
|
|
1139
|
+
measured with CIA2 timer A in VICE x64sc 3.10 (the spread is the page
|
|
1140
|
+
crossing of the sine lookup in some columns). The `**Cost:**` line above
|
|
1141
|
+
states that worst frame, the code segment (`$0900-$1914`, of which the
|
|
1142
|
+
unrolled copy is most) and the table segment (`$2000-$2441`) from
|
|
1143
|
+
KickAssembler's memory map; the 2 KB charset the copy writes is cleared
|
|
1144
|
+
at run time and is in neither segment.
|
|
1145
|
+
|
|
1146
|
+
Where in the frame the copy runs is the whole budget. A column's strip
|
|
1147
|
+
bytes are read by the VIC on every raster line of the band, so the copy
|
|
1148
|
+
must finish before the band's first line, or run into a charset the VIC
|
|
1149
|
+
is not showing. From an interrupt at line 250 the recipe's copy ends by
|
|
1150
|
+
line 35 on PAL and by line 84 on NTSC, against a band starting at line
|
|
1151
|
+
122. The NTSC figure is 49 lines later for the same CPU work because the
|
|
1152
|
+
263-line frame leaves 13 lines of blank after 250, so the copy runs over
|
|
1153
|
+
the top of the display and crosses three to five badlines, each 40 to 43
|
|
1154
|
+
cycles: the timer reads 5,433 to 5,556 on NTSC against 5,305 to 5,343 on
|
|
1155
|
+
PAL. Each extra column costs 136 cycles and about two lines; each extra
|
|
1156
|
+
strip cell costs eight slots and nothing per frame. The slot budget
|
|
1157
|
+
binds first: with 22 slots kept for a blank and a caption, 39 columns
|
|
1158
|
+
of six cells is 234, and a seventh cell would allow only 33 columns.
|
|
1159
|
+
|
|
1160
|
+
### Recipes
|
|
1161
|
+
|
|
1162
|
+
- `recipes/kickassembler/dycp-scroller.md`
|
|
1163
|
+
|
|
1164
|
+
---
|
|
1165
|
+
|
|
1166
|
+
## eight_way_scroll_double_buffer — Eight-way tile scroll over two screen matrices
|
|
1167
|
+
|
|
1168
|
+
**Complexity:** high
|
|
1169
|
+
**Region:** both
|
|
1170
|
+
**Uses registers:** D011, D012, D016, D018
|
|
1171
|
+
**Uses kernal:** (none)
|
|
1172
|
+
**Requires:** screen_double_buffer_d018, soft_scroll_h, soft_scroll_v
|
|
1173
|
+
**Cost:** cycles_per_frame=13152
|
|
1174
|
+
**Cost basis:** measured-vice
|
|
1175
|
+
|
|
1176
|
+
### Why
|
|
1177
|
+
|
|
1178
|
+
`soft_scroll_h` and `soft_scroll_v` together move the display by up to
|
|
1179
|
+
seven pixels in each axis. Past that the character grid has to move, and
|
|
1180
|
+
in eight directions that means the whole matrix, not an edge: a diagonal
|
|
1181
|
+
step changes every cell's contents, so the column shift that serves
|
|
1182
|
+
`char_scroll_buffer_h` has nothing to shift into.
|
|
1183
|
+
|
|
1184
|
+
A full 1,000-byte matrix rewrite does not fit the vertical blank on
|
|
1185
|
+
either model, and written into the live matrix it tears: the beam crosses
|
|
1186
|
+
the rewrite and shows the top of the old world above the bottom of the
|
|
1187
|
+
new. Two matrices in the same VIC bank solve it. The redraw goes into the
|
|
1188
|
+
one that is not on display, over as many fields as it needs, and the
|
|
1189
|
+
`$D018` VM nibble swaps them in the blank when it is finished.
|
|
1190
|
+
|
|
1191
|
+
### How
|
|
1192
|
+
|
|
1193
|
+
1. **Camera in world pixels.** Tile origin is `camx >> 3` and `camy >> 3`.
|
|
1194
|
+
XSCROLL moves the display right, so a rightward camera needs
|
|
1195
|
+
`7 - (camx & 7)` in `$D016` bits 0 to 2 and `7 - (camy & 7)` in `$D011`
|
|
1196
|
+
bits 0 to 2. Write both registers whole from a shadow rather than
|
|
1197
|
+
read-modify-write, or the store takes CSEL and MCM with it
|
|
1198
|
+
(`d016_unmasked_rmw_clobbers_csel_mcm`).
|
|
1199
|
+
2. **Both windows narrow.** RSEL 0 and CSEL 0 hide the partial row and
|
|
1200
|
+
column at each edge, so a 40 x 25 matrix covers the display at every
|
|
1201
|
+
fine scroll value.
|
|
1202
|
+
3. **Draw for the origin after next.** The spare matrix cannot be drawn
|
|
1203
|
+
for the origin the camera has now, because the camera moves while it is
|
|
1204
|
+
being drawn. Work out which axis crosses a tile boundary first, from
|
|
1205
|
+
`8 - (cam & 7)` when it is increasing and `(cam & 7) + 1` when it is
|
|
1206
|
+
decreasing, and draw for the origin that crossing will produce. On a
|
|
1207
|
+
diagonal the axes cross on different fields and one spare matrix cannot
|
|
1208
|
+
serve two origins, so only the nearer crossing is targeted.
|
|
1209
|
+
4. **Flip on a match, never on a hope.** The flip is taken only if the
|
|
1210
|
+
spare matrix already holds exactly the origin wanted. Count the
|
|
1211
|
+
refusals; a non-zero count is the scroll stuttering.
|
|
1212
|
+
5. **Colour RAM in four calls.** Colour RAM is not paged. Both halves are
|
|
1213
|
+
written in `irqColB` at raster 4, at most `BAND_MAX` rows per call.
|
|
1214
|
+
After a flip, the first top-half call uses `BAND_FIRST` rows (fewer
|
|
1215
|
+
than `BAND_MAX`) so it finishes before the first top rows' badlines even
|
|
1216
|
+
when `irqColB` starts late on NTSC. Four calls cover all 25 rows over
|
|
1217
|
+
four fields, producing three displayed fields of stale colour per
|
|
1218
|
+
crossing before all rows are updated.
|
|
1219
|
+
6. **Cap every band of the redraw.** Spread the matrix over the fields
|
|
1220
|
+
available, and put a ceiling on every field, not only the ones doing
|
|
1221
|
+
colour work.
|
|
1222
|
+
|
|
1223
|
+
### Why it works
|
|
1224
|
+
|
|
1225
|
+
The VIC reads the matrix at the address the VM nibble names, on the
|
|
1226
|
+
badline of each character row. Changing that nibble between fields
|
|
1227
|
+
changes which 1,000 bytes the next field reads, and nothing else: the
|
|
1228
|
+
fine scroll, the charset base and the bank are untouched. So a matrix
|
|
1229
|
+
drawn over several fields is invisible until the field it first appears
|
|
1230
|
+
in, and appears whole.
|
|
1231
|
+
|
|
1232
|
+
Sub-tile and whole-tile motion are the same motion split at the eight
|
|
1233
|
+
pixel boundary. `camx` is the only state; the fine scroll is its low three
|
|
1234
|
+
bits inverted and the origin its high bits, so they can never disagree.
|
|
1235
|
+
|
|
1236
|
+
Colour RAM is the part that cannot be double buffered, and it is why the
|
|
1237
|
+
technique is a raster problem rather than a blank-interval one. All 25
|
|
1238
|
+
rows are written in four calls of `irqColB`, which fires at raster 4 each
|
|
1239
|
+
field. The first call is limited to `BAND_FIRST` rows so it finishes
|
|
1240
|
+
before row 6's badline, which is what the verdict checks; rows 0 to 4 can
|
|
1241
|
+
still show old colour for one field on NTSC when `irqColB` starts late.
|
|
1242
|
+
The matrix appears whole on the flip field; the colour
|
|
1243
|
+
takes three more displayed fields to catch up, producing a transient
|
|
1244
|
+
mismatch of about 60 ms PAL and 50 ms NTSC per crossing.
|
|
1245
|
+
|
|
1246
|
+
### Variations
|
|
1247
|
+
|
|
1248
|
+
- **AGSP.** Rewriting `$D011` and `$D016` per raster line, and the VM
|
|
1249
|
+
nibble mid-frame, gives a hardware-scrolled playfield without any matrix
|
|
1250
|
+
redraw at all, at the price of a per-line interrupt and a much harder
|
|
1251
|
+
stability problem. It is the standard answer when the whole screen
|
|
1252
|
+
scrolls and nothing else needs the CPU.
|
|
1253
|
+
- **A panel that does not scroll.** A world of 25 rows plus a status area
|
|
1254
|
+
wants the split raster to become a mode change rather than a colour
|
|
1255
|
+
deadline; see `scroll_panel_split`.
|
|
1256
|
+
- **Colour in one page.** If the world is one colour per screen, or the
|
|
1257
|
+
colour changes only on a flip that also changes the palette, the two
|
|
1258
|
+
halves collapse to nothing and the budget roughly halves.
|
|
1259
|
+
- **Smaller worlds.** A world of 40 x 25 tiles or less needs no redraw at
|
|
1260
|
+
all, only the fine scroll and a wrap.
|
|
1261
|
+
|
|
1262
|
+
### Cycle budget
|
|
1263
|
+
|
|
1264
|
+
Measured in VICE x64sc 3.10 over runs of 20,946,000 cycles on each model,
|
|
1265
|
+
with CIA 1 timer B read on entry to and exit from each of the three
|
|
1266
|
+
handlers and the per-field totals compared, so badline stalls are
|
|
1267
|
+
included:
|
|
1268
|
+
|
|
1269
|
+
| Model | Field | Worst field measured | Headroom |
|
|
1270
|
+
|---|---|---|---|
|
|
1271
|
+
| PAL | 19,656 | 13,111 | 6,545 |
|
|
1272
|
+
| NTSC | 17,095 | 13,152 | 3,943 |
|
|
1273
|
+
|
|
1274
|
+
The worst field is the one that writes seven colour rows in `irqColB` and
|
|
1275
|
+
seven matrix rows in `irqPrep`. A copy loop of `lda abs,x` / `sta abs,x`
|
|
1276
|
+
/ `dex` / `bpl` is 14 cycles a byte in instruction terms and measures
|
|
1277
|
+
17.6 to 17.8 with badlines (measured-vice, 25-row draw, 1,000 bytes, CIA
|
|
1278
|
+
timer), so a 40-byte row costs around 710 cycles.
|
|
1279
|
+
|
|
1280
|
+
`BAND_MAX = 7` keeps every call within the available window: the prep
|
|
1281
|
+
handler has 92 rasters from 152 + YSCROLL to 251, and the colour handler
|
|
1282
|
+
has from raster 4 to 152 + YSCROLL. The first colour call uses
|
|
1283
|
+
`BAND_FIRST = 5` rows instead of `BAND_MAX` to fit within NTSC's late
|
|
1284
|
+
start. At half speed (one pixel every two fields), tile crossings are
|
|
1285
|
+
sixteen fields apart on straight legs. The requirement is five fields: four
|
|
1286
|
+
matrix preps plus four colour calls finish in the same four-field span, and
|
|
1287
|
+
a flip is possible from the fifth field. The six-field minimum gap leaves
|
|
1288
|
+
one field of slack.
|
|
1289
|
+
|
|
1290
|
+
### Recipes
|
|
1291
|
+
|
|
1292
|
+
- `recipes/kickassembler/eight-way-scroll.md`
|