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,979 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: sprite
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- doc-type: pitfall-reference -->
|
|
6
|
+
|
|
7
|
+
# Sprite Pitfalls
|
|
8
|
+
|
|
9
|
+
The VIC-II provides exactly eight hardware sprites. Every sprite technique
|
|
10
|
+
that goes beyond this hard limit — multiplexers, Y-stretch, mid-frame
|
|
11
|
+
recoloring — requires tight coordination between CPU writes and the VIC's
|
|
12
|
+
internal state machine. The pitfalls in this document describe the most
|
|
13
|
+
common failures: silent drops when too many sprites share a line, visual
|
|
14
|
+
artifacts when expansion state changes at the wrong moment, coordinate
|
|
15
|
+
teleportation when the 9th X bit is forgotten, collisions that vanish
|
|
16
|
+
before the CPU can read them, and sprites the game counts while the border
|
|
17
|
+
hides them. All of them have bitten experienced C64 coders.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## sprite_dma_overflow — A logical sprite re-armed while its hardware slot is still busy, or after its Y line has passed, is silently not drawn
|
|
22
|
+
|
|
23
|
+
**Severity:** critical
|
|
24
|
+
**Region:** both
|
|
25
|
+
**Triggered by registers:** D015, D010, D000, D001
|
|
26
|
+
**Triggered by techniques:** sprite_multiplex_8, stable_raster_irq, sprite_multiplex_24, sprite_sine_chain, sprite_multiplex_game, dot_3d_rotator
|
|
27
|
+
**Mitigated by techniques:** sprite_multiplex_8
|
|
28
|
+
|
|
29
|
+
### Symptom
|
|
30
|
+
|
|
31
|
+
A multiplexed sprite engine that is supposed to show twelve, sixteen, or
|
|
32
|
+
more objects on screen displays only the first eight on any given raster
|
|
33
|
+
line. Logical sprites whose slot was re-armed too early or too late silently
|
|
34
|
+
disappear; in a naively ordered multiplexer these are the later entries in
|
|
35
|
+
the list, which is a software ordering, not a hardware one. There is no
|
|
36
|
+
visible corruption or tearing — sprites simply are not drawn, as if they
|
|
37
|
+
were never enabled.
|
|
38
|
+
The bug is easy to miss when objects are spread vertically because each
|
|
39
|
+
frame only a few lines have more than eight sprites active simultaneously.
|
|
40
|
+
The effect becomes obvious when objects cluster near the same Y coordinate:
|
|
41
|
+
a game with many bullets or enemies at the same height suddenly loses half
|
|
42
|
+
its sprites.
|
|
43
|
+
|
|
44
|
+
### Mechanism
|
|
45
|
+
|
|
46
|
+
The VIC-II has exactly eight sprite DMA channels — one per sprite index
|
|
47
|
+
(0 through 7). On each raster line the chip checks every enabled sprite's Y
|
|
48
|
+
register against the current raster line and, for any slot whose 21-line
|
|
49
|
+
(42 if Y-expanded) DMA is already running, fetches that slot's three data
|
|
50
|
+
bytes via the s-access cycles for that channel; the eight slots are
|
|
51
|
+
independent and no slot has priority over another for activation. There
|
|
52
|
+
are no additional DMA channels hiding behind software flags. The hardware
|
|
53
|
+
simply has eight slots, and eight is the absolute maximum simultaneously
|
|
54
|
+
active sprites per raster line.
|
|
55
|
+
|
|
56
|
+
A multiplexer that repositions sprites by writing new Y coordinates between
|
|
57
|
+
groups relies on each hardware slot being free when its next logical sprite
|
|
58
|
+
is due. The chip compares each enabled slot's Y with the raster line late in
|
|
59
|
+
every line (cycles 55-56 in Bauer's timing; measured in VICE, a Y write
|
|
60
|
+
landing before roughly cycle 55 of the target line still starts the sprite
|
|
61
|
+
on the next line) and starts the sprite's DMA only if that slot's DMA is
|
|
62
|
+
currently off. Two things therefore lose a sprite, without error, without a
|
|
63
|
+
flag, and without any signal visible to the programmer: a slot re-armed to a
|
|
64
|
+
Y that matches while the slot's own DMA is still running (the incoming
|
|
65
|
+
sprite is skipped for the rest of the frame — measured in VICE x64sc, a slot
|
|
66
|
+
at Y=100 rewritten on line 112 to Y=120 never re-appeared, while Y=121 did),
|
|
67
|
+
and a Y written after the raster has already passed it, which the compare
|
|
68
|
+
never matches again that frame. $D015 has only eight bits; the chip never
|
|
69
|
+
"sees nine enabled indices", and no index has priority over another. (An
|
|
70
|
+
earlier version of this paragraph said the chip walks indices 0-7 in
|
|
71
|
+
ascending order and always discards the highest-indexed sprite on a
|
|
72
|
+
contested line; the drop is per slot and depends on timing, not on index.)
|
|
73
|
+
|
|
74
|
+
`sprite_multiplex_8` is on both metadata lines above for that reason: the
|
|
75
|
+
overflow arises inside a naive multiplexer — one whose IRQ fires late or
|
|
76
|
+
whose sort leaves two groups on one line — and a correct one, sorted and
|
|
77
|
+
spaced as the Fix describes, is what prevents it.
|
|
78
|
+
|
|
79
|
+
### Fix
|
|
80
|
+
|
|
81
|
+
The fix is a correct sprite multiplexer running off raster IRQs, with two
|
|
82
|
+
requirements that must both hold:
|
|
83
|
+
|
|
84
|
+
1. **Y-sorted logical sprites.** Before each frame, sort the entire logical
|
|
85
|
+
sprite list by ascending Y position. This ensures that when you assign
|
|
86
|
+
the first eight logical sprites to hardware slots 0-7 and schedule the
|
|
87
|
+
next IRQ for the ninth sprite's Y position, the groups are already
|
|
88
|
+
separated along the Y axis.
|
|
89
|
+
|
|
90
|
+
2. **Minimum gap between groups.** Each group must begin at least one
|
|
91
|
+
raster line below the bottom of the previous group. The VIC considers a
|
|
92
|
+
sprite "active" for 21 lines (or 42 lines when Y-expanded). Schedule
|
|
93
|
+
the reuse IRQ to fire at or after the last line of the outgoing group
|
|
94
|
+
plus one, so the channel is free by the time the next logical sprite's Y
|
|
95
|
+
is reached. Aim for 3-4 lines of slack to leave room for IRQ jitter and
|
|
96
|
+
the cycle cost of the register writes themselves.
|
|
97
|
+
|
|
98
|
+
### Worked example
|
|
99
|
+
|
|
100
|
+
The following KickAssembler skeleton shows the channel-reuse pattern.
|
|
101
|
+
Hardware sprite slots 0-7 are assigned to logical sprites 0-7 at frame
|
|
102
|
+
start. When the raster counter reaches the Y of logical sprite 8, an IRQ
|
|
103
|
+
reprograms all eight hardware slots to serve logical sprites 8-15.
|
|
104
|
+
|
|
105
|
+
```kickassembler
|
|
106
|
+
// --- Frame start (vertical blank) ---
|
|
107
|
+
// Write first 8 logical sprites to hardware slots 0-7
|
|
108
|
+
.for (var i = 0; i < 8; i++) {
|
|
109
|
+
lda logicY + i // Y position of logical sprite i
|
|
110
|
+
sta $D001 + i * 2 // hardware sprite i Y register
|
|
111
|
+
lda logicX + i // X position (low byte)
|
|
112
|
+
sta $D000 + i * 2
|
|
113
|
+
lda logicPtr + i
|
|
114
|
+
sta SPRITE_PTRS + i // video matrix + $3F8
|
|
115
|
+
lda logicColor + i
|
|
116
|
+
sta $D027 + i
|
|
117
|
+
}
|
|
118
|
+
lda #$FF
|
|
119
|
+
sta $D015 // enable all 8 hardware sprites
|
|
120
|
+
|
|
121
|
+
// Schedule IRQ for group 2 (logical sprites 8-15)
|
|
122
|
+
// Fire one line above the topmost sprite in the next group
|
|
123
|
+
lda logicY + 8
|
|
124
|
+
sec
|
|
125
|
+
sbc #4 // 4-line slack
|
|
126
|
+
sta $D012 // raster compare
|
|
127
|
+
lda #$01
|
|
128
|
+
sta $D01A // enable raster IRQ
|
|
129
|
+
|
|
130
|
+
// --- Raster IRQ handler (fires above Y of logical sprite 8) ---
|
|
131
|
+
irq_group2:
|
|
132
|
+
pha
|
|
133
|
+
txa
|
|
134
|
+
pha
|
|
135
|
+
tya
|
|
136
|
+
pha
|
|
137
|
+
|
|
138
|
+
lda #$01
|
|
139
|
+
sta $D019 // acknowledge VIC IRQ
|
|
140
|
+
|
|
141
|
+
.for (var i = 0; i < 8; i++) {
|
|
142
|
+
lda logicY + 8 + i
|
|
143
|
+
sta $D001 + i * 2 // repoint hardware slot i → logical sprite 8+i
|
|
144
|
+
lda logicX + 8 + i
|
|
145
|
+
sta $D000 + i * 2
|
|
146
|
+
lda logicPtr + 8 + i
|
|
147
|
+
sta SPRITE_PTRS + i
|
|
148
|
+
lda logicColor + 8 + i
|
|
149
|
+
sta $D027 + i
|
|
150
|
+
}
|
|
151
|
+
// Update MSB X bits for the new group
|
|
152
|
+
lda xMsbGroup2
|
|
153
|
+
sta $D010
|
|
154
|
+
|
|
155
|
+
pla
|
|
156
|
+
tay
|
|
157
|
+
pla
|
|
158
|
+
tax
|
|
159
|
+
pla
|
|
160
|
+
rti
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Critical invariant: every element of `logicY[8..15]` that reuses a hardware
|
|
164
|
+
slot must be at least 21 lines below that slot's previous Y (42 if
|
|
165
|
+
Y-expanded). Measured in VICE x64sc 3.10: a slot rewritten to old Y + 21
|
|
166
|
+
re-displays on the very next line with no gap, old Y + 20 never appears
|
|
167
|
+
again that frame, old Y + 22 leaves one blank line (Y-expanded: + 42
|
|
168
|
+
seamless, + 41 dropped). The mechanism — the outgoing sprite's DMA switches
|
|
169
|
+
off in cycle 16 of line Y + 21 and the Y compare that would re-arm the slot
|
|
170
|
+
runs in cycle 55 of that same line — is from Bauer's VIC article, not
|
|
171
|
+
measured here. An earlier revision of this paragraph said 22 and claimed a
|
|
172
|
+
21-line gap dropped a sprite; it does not. The 3-4 lines of slack in the Fix
|
|
173
|
+
come on top of the 21 for a different reason: the IRQ at `logicY[8] - 4`
|
|
174
|
+
also rewrites the slot's X, pointer and colour, and those act on the
|
|
175
|
+
still-running outgoing sprite from the next line (a colour write on line 112
|
|
176
|
+
recolours a Y = 100 sprite's rows 113-121, measured in VICE), so with that
|
|
177
|
+
IRQ scheme the practical same-slot spacing is 21 plus the slack, and a
|
|
178
|
+
20-line gap silently drops the incoming sprite for that frame.
|
|
179
|
+
|
|
180
|
+
### Cross-references
|
|
181
|
+
|
|
182
|
+
- Technique: `sprite_multiplex_8` — the sorted-Y, raster-IRQ multiplexer
|
|
183
|
+
that prevents overflow
|
|
184
|
+
- Technique: `sprite_multiplex_24` — extends the pattern to 24+ sprites
|
|
185
|
+
across three reuse passes
|
|
186
|
+
- Register: `D015` — sprite enable bitmask
|
|
187
|
+
- Registers: `D000`–`D00F` — per-sprite X/Y position
|
|
188
|
+
- Register: `D010` — sprite X MSB (see also `sprite_x_high_bit_wrong_register`)
|
|
189
|
+
- Pitfall: `sprite_x_high_bit_wrong_register` — a related issue when moving
|
|
190
|
+
sprites past X=255
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## sprite_y_expand_double_register_write — Clearing Y-expand mid-display crunches the sprite if the write lands on one cycle; a second write does not help
|
|
195
|
+
|
|
196
|
+
**Severity:** high
|
|
197
|
+
**Region:** both
|
|
198
|
+
**Triggered by registers:** D017
|
|
199
|
+
**Triggered by techniques:** stable_raster_irq, sprite_expand, sprite_y_stretch_glitch, sprite_stretcher_d017
|
|
200
|
+
|
|
201
|
+
### Symptom
|
|
202
|
+
|
|
203
|
+
A sprite that is supposed to switch from Y-expanded to normal height
|
|
204
|
+
mid-frame, while it is still in the display area, is occasionally the wrong
|
|
205
|
+
length: it comes out of the switch with its rows out of order and ends many
|
|
206
|
+
lines lower than it should — the "sprite crunch". The fault depends on the
|
|
207
|
+
exact cycle the $D017 write lands on within the raster line, so it comes and
|
|
208
|
+
goes with IRQ jitter: at almost every cycle position the switch is clean
|
|
209
|
+
(the sprite simply continues unexpanded from its current row), and at one
|
|
210
|
+
cycle position it crunches. An earlier version of this section described a
|
|
211
|
+
one-line artifact — a single repeated or skipped row at the line of the
|
|
212
|
+
write — on every mid-sprite write. Measured in VICE x64sc 3.10 PAL, no such
|
|
213
|
+
artifact exists: a clear at 62 of 64 cycle positions gave a clean switch
|
|
214
|
+
with no repeated or skipped row, and the remaining 2 positions gave the
|
|
215
|
+
crunch (a +21-line change, rows re-fetched out of order). A sentence here
|
|
216
|
+
about a subtler "half-pixel shift" on the 8565 was removed as unverifiable
|
|
217
|
+
on this machine: every run was on one model, VICE's default C64C (VIC-II
|
|
218
|
+
8565), and none was compared with a 6569. An earlier version of this
|
|
219
|
+
sentence said VICE was run as a 6569 only.
|
|
220
|
+
|
|
221
|
+
### Mechanism
|
|
222
|
+
|
|
223
|
+
The VIC-II implements Y expansion with a per-sprite expansion flip-flop
|
|
224
|
+
(the "advance line" / expansion flip-flop of Bauer's VIC-II article;
|
|
225
|
+
`exp_flop` in the VICE source). While the sprite's $D017 bit is clear the
|
|
226
|
+
flip-flop is held set; while the bit is set and the sprite's DMA is on, it
|
|
227
|
+
is inverted once per line (Bauer places this in cycle 55; VICE 3.10's PAL
|
|
228
|
+
cycle table, as read for `sprite_y_stretch_glitch` in
|
|
229
|
+
`docs/techniques/sprite.md`, at cycle 56). In cycle 16 of the following
|
|
230
|
+
line the 6-bit sprite data counter base MCBASE is loaded from the data
|
|
231
|
+
counter MC — moving the sprite on to its next 3-byte row — only if the
|
|
232
|
+
flip-flop is set; otherwise MCBASE is left alone and the same row is
|
|
233
|
+
fetched again. MCBASE is a counter, not the toggle; neither it nor the
|
|
234
|
+
flip-flop appears in the Programmer's Reference Guide. (An earlier version
|
|
235
|
+
of this paragraph called the toggle itself "MCBASE".)
|
|
236
|
+
|
|
237
|
+
A $D017 clear that lands anywhere else in the line does nothing worse than
|
|
238
|
+
set the flip-flop: from the next cycle-16 step the sprite advances a row
|
|
239
|
+
every line and finishes as a plain unexpanded sprite from its current row.
|
|
240
|
+
The problem is one cycle only. If the clear lands on cycle 15 (VICE 3.10's
|
|
241
|
+
PAL cycle table) of one of the sprite's display lines after the first —
|
|
242
|
+
immediately before the cycle-16 MCBASE step — the chip does not handle the
|
|
243
|
+
transition cleanly and MCBASE is loaded with a blend of its old value and
|
|
244
|
+
MC rather than either; the sprite's remaining length changes once, by a
|
|
245
|
+
data-dependent amount, and the rows come out of order. This is the "sprite
|
|
246
|
+
crunch" that `sprite_y_stretch_glitch` exploits deliberately; here it is an
|
|
247
|
+
accidental side effect of an otherwise normal register update. Which line
|
|
248
|
+
the write must land on is characterised in that technique entry (it
|
|
249
|
+
measured the second display line of a sprite at Y=100); in the
|
|
250
|
+
verification of this entry the crunching clear was pinned to raster line
|
|
251
|
+
169 for a Y-expanded sprite at Y=150, a line on which the expanded sprite
|
|
252
|
+
would otherwise have repeated its row. State the cycle; whether the line
|
|
253
|
+
is one on which MCBASE advances has not been measured and is not claimed.
|
|
254
|
+
|
|
255
|
+
### Fix
|
|
256
|
+
|
|
257
|
+
The fix is to control *where in the line* the single $D017 write lands, not
|
|
258
|
+
to write the register twice. Fire a stable raster IRQ on the target line and
|
|
259
|
+
issue one `sta $D017` at a known cycle position that is not the crunch
|
|
260
|
+
cycle; anywhere on the sprite's first display line, or on a line before the
|
|
261
|
+
sprite starts, is also safe. A second write does not help: an earlier
|
|
262
|
+
version of this entry prescribed a "double-write trick" — two back-to-back
|
|
263
|
+
STAs to "resynchronize" the toggle — and said a single write produced a
|
|
264
|
+
one-line repeated or skipped row. A 64-position single-vs-double sweep in
|
|
265
|
+
VICE x64sc 3.10 PAL (a clear of one sprite's bit at every cycle position of
|
|
266
|
+
a display line, once as `sta $D017 / nop / nop` and once as
|
|
267
|
+
`sta $D017 / sta $D017` at the identical first-write cycle) found the two
|
|
268
|
+
identical at every position, including the crunch cycle, where the double
|
|
269
|
+
write produced byte-for-byte the same crunch. The earlier text also said
|
|
270
|
+
the second write came "one CPU cycle later"; two consecutive `sta $D017`
|
|
271
|
+
instructions in fact write four cycles apart (STA abs is 4 cycles and the
|
|
272
|
+
write is its last cycle), well past the cycle-16 step it would have needed
|
|
273
|
+
to influence. Do not reach for a read-modify-write (`inc`/`dec`/`asl
|
|
274
|
+
$D017`) to get consecutive-cycle writes either: an RMW does write twice on
|
|
275
|
+
consecutive cycles, but its first write is the old register value.
|
|
276
|
+
|
|
277
|
+
If the sprite's Y expand state only needs to change between frames (not
|
|
278
|
+
mid-frame), the simplest moment is during vertical blank, before the
|
|
279
|
+
sprite's Y position comes into view: the sprite's DMA is off, so the
|
|
280
|
+
expansion flip-flop is held set and the crunch cycle cannot be hit.
|
|
281
|
+
|
|
282
|
+
### Worked example
|
|
283
|
+
|
|
284
|
+
The following KickAssembler snippet fires a stable raster IRQ one line
|
|
285
|
+
above the sprite's current top edge, then issues a single write to flip
|
|
286
|
+
sprite 3's Y-expand bit off cleanly. On that line the sprite's DMA has not
|
|
287
|
+
started, so the write cannot land on the crunch cycle. (An earlier version
|
|
288
|
+
of this listing wrote $D017 twice; the second write changed nothing in a
|
|
289
|
+
64-position VICE sweep and has been removed.)
|
|
290
|
+
|
|
291
|
+
```kickassembler
|
|
292
|
+
// Stable raster IRQ fires at rasterTarget (one line above sprite top)
|
|
293
|
+
// SP3_Y is the sprite's current Y position; IRQ fires at SP3_Y - 1
|
|
294
|
+
|
|
295
|
+
irq_clear_yexpand:
|
|
296
|
+
pha
|
|
297
|
+
txa
|
|
298
|
+
pha
|
|
299
|
+
|
|
300
|
+
lda #$01
|
|
301
|
+
sta $D019 // acknowledge VIC raster IRQ
|
|
302
|
+
|
|
303
|
+
// Single write: clear bit 3 of $D017 (sprite 3 Y-expand off)
|
|
304
|
+
lda #%11110111 // new value: sprite 3 bit clear, others unchanged
|
|
305
|
+
sta $D017 // one write is enough; the flip-flop is held set from here
|
|
306
|
+
|
|
307
|
+
// Re-arm IRQ for next occurrence
|
|
308
|
+
lda #<nextIrqLine
|
|
309
|
+
sta $D012
|
|
310
|
+
|
|
311
|
+
pla
|
|
312
|
+
tax
|
|
313
|
+
pla
|
|
314
|
+
rti
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
If the target is to toggle expansion *on* (set the bit) rather than off,
|
|
318
|
+
the same single write applies with the appropriate mask. Only the *clear*
|
|
319
|
+
transition was swept for the crunch in the verification above; setting the
|
|
320
|
+
bit mid-sprite was not measured here and is not claimed to be safe at every
|
|
321
|
+
cycle.
|
|
322
|
+
|
|
323
|
+
For situations where multiple sprites need simultaneous Y-expand state
|
|
324
|
+
changes, include all sprite bits in the mask value and issue the one write.
|
|
325
|
+
A single STA covering all eight sprites is sufficient; one write per sprite
|
|
326
|
+
is wasteful and risks introducing timing skew between sprites. (An earlier
|
|
327
|
+
version of these two paragraphs said both transitions "require the
|
|
328
|
+
double-write"; see the Fix above for the measurement that retired it.)
|
|
329
|
+
|
|
330
|
+
### Cross-references
|
|
331
|
+
|
|
332
|
+
- Technique: `stable_raster_irq` — prerequisite for placing the $D017
|
|
333
|
+
write at a known horizontal cycle position, away from the crunch cycle
|
|
334
|
+
- Technique: `sprite_y_stretch_glitch` — the intentional exploitation of
|
|
335
|
+
the same expansion flip-flop / MCBASE-load quirk to produce tall sprites
|
|
336
|
+
- Register: `D017` — sprite Y-expand control register
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## sprite_x_high_bit_wrong_register — Sprite teleports to far left when X crosses 255 because $D010 MSB is not updated
|
|
341
|
+
|
|
342
|
+
**Severity:** medium
|
|
343
|
+
**Region:** both
|
|
344
|
+
**Triggered by registers:** D010
|
|
345
|
+
**Triggered by techniques:** sprite_sine_chain, sprite_multiplex_24, logic_rate_decoupling, sprite_multiplex_game, actor_activation_window, per_frame_hitbox, wave_director, multi_sprite_object, flip_screen_rooms, mixed_sprite_char_actors, lane_depth_engine, dot_3d_rotator, starfield, dypp_sprite_sine_scroller, sprite_border_scroller, sprites_only_screen_mode, dysp_side_border_sprites
|
|
346
|
+
|
|
347
|
+
### Symptom
|
|
348
|
+
|
|
349
|
+
A sprite moving smoothly from left to right across the screen suddenly
|
|
350
|
+
teleports from near the right edge back to the far left when its X
|
|
351
|
+
coordinate crosses 256. The motion is otherwise smooth in both the
|
|
352
|
+
sub-256 and the 256+ ranges. The jump happens exactly at the X=256
|
|
353
|
+
boundary and reverses at the same boundary when moving right-to-left.
|
|
354
|
+
In a game this manifests as an enemy or projectile that vanishes off
|
|
355
|
+
the right side of the screen and reappears at the left at the same Y
|
|
356
|
+
position — as if the coordinate space wraps at 256.
|
|
357
|
+
|
|
358
|
+
### Mechanism
|
|
359
|
+
|
|
360
|
+
The VIC-II uses a 9-bit X coordinate for each sprite. The low 8 bits live
|
|
361
|
+
in the per-sprite register at `$D000 + sprite * 2` (sprite 0 → $D000,
|
|
362
|
+
sprite 1 → $D002, ..., sprite 7 → $D00E). The 9th bit — the MSB —
|
|
363
|
+
does not live in those per-sprite registers. All eight MSBs are packed
|
|
364
|
+
into a single shared register: **$D010 (MSIGX)**. Bit 0 of $D010 is the
|
|
365
|
+
MSB for sprite 0, bit 1 for sprite 1, and so on through bit 7 for sprite 7.
|
|
366
|
+
|
|
367
|
+
The common mistake is to maintain a 9-bit (or 16-bit) X variable in game
|
|
368
|
+
code but write only the low 8 bits to `$D000 + sprite * 2`, forgetting
|
|
369
|
+
$D010 entirely. When X < 256 this is invisible — the MSB is 0 and $D010's
|
|
370
|
+
corresponding bit is already 0 (or was never set). When X reaches 256, the
|
|
371
|
+
low 8 bits wrap to 0 and the hardware X coordinate becomes 0 with the MSB
|
|
372
|
+
still clear — the sprite teleports to X=0 on screen. Setting the MSB in
|
|
373
|
+
$D010 when X ≥ 256 would place the sprite correctly at the screen position
|
|
374
|
+
corresponding to the full 9-bit value, but the write never happens.
|
|
375
|
+
|
|
376
|
+
Sprite X is a 9-bit coordinate, 0-511, in the VIC-II's own pixel space. The
|
|
377
|
+
320-pixel display window spans X=24..343 (X=31..334 with CSEL=0); X=0 is 24
|
|
378
|
+
pixels into the left border, so a sprite needs X ≥ 24 to be fully inside
|
|
379
|
+
the window, and the right-hand 88 pixels of the window (X=256..343) are
|
|
380
|
+
reachable only with the $D010 bit set. A sprite at X=256 therefore sits
|
|
381
|
+
about 232 pixels from the window's left edge and is fully visible; it is
|
|
382
|
+
still fully visible at X=320 (occupying 320..343), begins to be covered by
|
|
383
|
+
the right border from X=321, and is entirely hidden in the border from
|
|
384
|
+
X=344 (measured in VICE x64sc: X=256 renders at VIC X 256..279, screenshot
|
|
385
|
+
x 264-287). Note the helper below already assumes this range (0-343). An
|
|
386
|
+
earlier version of this paragraph put X=0 at the window's left edge and
|
|
387
|
+
X=256 "slightly past the right edge", with X=256..344 clipped; both were
|
|
388
|
+
wrong.
|
|
389
|
+
|
|
390
|
+
### Fix
|
|
391
|
+
|
|
392
|
+
Always update $D010 when moving any sprite across the X=256 boundary. The
|
|
393
|
+
correct pattern is a read-modify-write: read the current $D010 value, set
|
|
394
|
+
or clear the bit corresponding to the sprite being moved, then write the
|
|
395
|
+
modified value back. Never assume $D010 is 0 — other sprites in the same
|
|
396
|
+
frame may have their MSB bits set.
|
|
397
|
+
|
|
398
|
+
In C (Oscar64), a helper macro handles the 9-bit write atomically:
|
|
399
|
+
|
|
400
|
+
```c
|
|
401
|
+
// Set sprite n to 9-bit X position xpos (0-343)
|
|
402
|
+
static inline void sprite_set_x(uint8_t n, uint16_t xpos) {
|
|
403
|
+
*(volatile uint8_t*)(0xD000 + n * 2) = (uint8_t)xpos; // low 8 bits
|
|
404
|
+
if (xpos >= 256)
|
|
405
|
+
*(volatile uint8_t*)0xD010 |= (1 << n); // set MSB
|
|
406
|
+
else
|
|
407
|
+
*(volatile uint8_t*)0xD010 &= ~(1 << n); // clear MSB
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
In KickAssembler, a macro that operates on a 16-bit X variable stored
|
|
412
|
+
in zero page:
|
|
413
|
+
|
|
414
|
+
```kickassembler
|
|
415
|
+
// spriteSetX: set hardware sprite .sprNum to 9-bit X in .xLo / .xHi (0 or 1)
|
|
416
|
+
// .sprNum = sprite index 0-7 (literal)
|
|
417
|
+
// xLo = zero-page address of low byte, xHi = adjacent high byte (0 or 1)
|
|
418
|
+
.macro spriteSetX(sprNum, xLo, xHi) {
|
|
419
|
+
lda xLo
|
|
420
|
+
sta $D000 + sprNum * 2 // write low 8 bits
|
|
421
|
+
|
|
422
|
+
lda xHi // high byte is 0 or 1 (only bit 0 relevant)
|
|
423
|
+
beq !setMsbZero+ // if 0, clear the MSB bit
|
|
424
|
+
|
|
425
|
+
lda $D010
|
|
426
|
+
ora #(1 << sprNum) // set MSB bit for this sprite
|
|
427
|
+
sta $D010
|
|
428
|
+
jmp !done+
|
|
429
|
+
|
|
430
|
+
!setMsbZero:
|
|
431
|
+
lda $D010
|
|
432
|
+
and #~(1 << sprNum) // clear MSB bit for this sprite
|
|
433
|
+
sta $D010
|
|
434
|
+
|
|
435
|
+
!done:
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
For multiplexed sprites that reassign hardware channels mid-frame, rebuild
|
|
440
|
+
the entire $D010 byte during each IRQ handler pass — accumulate the MSB
|
|
441
|
+
bits for all eight currently-assigned logical sprites and write the combined
|
|
442
|
+
mask once. This avoids stale MSB bits from the previous hardware assignment
|
|
443
|
+
lingering in $D010.
|
|
444
|
+
|
|
445
|
+
### Cross-references
|
|
446
|
+
|
|
447
|
+
- Register: `D010` — MSIGX, the sprite X position MSB register
|
|
448
|
+
- Registers: `D000`–`D00E` — per-sprite X position low bytes
|
|
449
|
+
- Technique: `sprite_multiplex_24` — shows `vspr_update()` accumulating
|
|
450
|
+
`xymask` ($D010) across all reuse passes
|
|
451
|
+
- Pitfall: `sprite_dma_overflow` — a related issue when more than 8 sprites
|
|
452
|
+
share a raster line
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## sprite_priority_collision_silent — Collision events vanish because $D01E/$D01F are read-to-clear and the read happens too late
|
|
457
|
+
|
|
458
|
+
**Severity:** medium
|
|
459
|
+
**Region:** both
|
|
460
|
+
**Triggered by registers:** D01E, D01F, D019
|
|
461
|
+
**Triggered by techniques:** sprite_collision_detect, mob_priority
|
|
462
|
+
|
|
463
|
+
### Symptom
|
|
464
|
+
|
|
465
|
+
The game's collision detection misses hits that visually occurred. Two
|
|
466
|
+
sprites clearly overlapped on screen but the game did not react — the
|
|
467
|
+
enemy was not destroyed, the player did not take damage. The bug is
|
|
468
|
+
intermittent: most collisions register correctly, but some are silently
|
|
469
|
+
lost, especially during busy frames with many simultaneous overlaps or
|
|
470
|
+
when the game logic runs late in the frame. Occasionally the reverse
|
|
471
|
+
happens: a collision is detected a frame after the visual overlap, making
|
|
472
|
+
the response feel one frame delayed.
|
|
473
|
+
|
|
474
|
+
A related symptom appears when the programmer reads $D01E or $D01F in an
|
|
475
|
+
interrupt handler and also checks it in the main loop — the main-loop check
|
|
476
|
+
always sees zero because the interrupt already cleared the register.
|
|
477
|
+
|
|
478
|
+
### Mechanism
|
|
479
|
+
|
|
480
|
+
$D01E (SPSPCL — sprite-to-sprite collision) and $D01F (SPBGCL —
|
|
481
|
+
sprite-to-background collision) are latched registers. The VIC-II sets
|
|
482
|
+
individual bits during raster rendering as collisions are detected in
|
|
483
|
+
hardware. Each bit, once set, stays set until the CPU reads the register.
|
|
484
|
+
The act of reading the register — any read, from any address mode, at any
|
|
485
|
+
privilege level — clears all bits in that register simultaneously. This
|
|
486
|
+
is the "read-to-clear" mechanic.
|
|
487
|
+
|
|
488
|
+
The latching behavior means that collisions accumulate: if sprite 0 hits
|
|
489
|
+
sprite 3 on line 80 and sprite 0 hits sprite 5 on line 120, by the time
|
|
490
|
+
the CPU reads $D01E at the end of the frame, bit 0, bit 3, and bit 5 are
|
|
491
|
+
all set (sprite 5's bit is set too, since both sprites involved in each
|
|
492
|
+
collision have their bits latched). A single read at any point after line
|
|
493
|
+
80 captures all of this correctly — provided nothing else read $D01E first.
|
|
494
|
+
|
|
495
|
+
Two patterns cause silent losses:
|
|
496
|
+
|
|
497
|
+
**Double-read:** An interrupt handler reads $D01E to check for a collision.
|
|
498
|
+
This clears the register. Later, the main loop reads $D01E again — it sees
|
|
499
|
+
zero. The collision events from that frame are gone. This is especially
|
|
500
|
+
common when a VIC IRQ is used to detect collisions in the IRQ handler and
|
|
501
|
+
the main loop independently polls the same register.
|
|
502
|
+
|
|
503
|
+
**Late read with prior clear:** If any code path reads $D01E or $D01F as a
|
|
504
|
+
side effect (even a "harmless" diagnostic read, or a read inside a debugger
|
|
505
|
+
print routine), that read destroys the accumulated collision state. A
|
|
506
|
+
diagnostic that works fine on its own can suppress collision detection when
|
|
507
|
+
enabled.
|
|
508
|
+
|
|
509
|
+
A second, subtler issue: the registers report *which sprites* were involved
|
|
510
|
+
in collisions, not *which specific pair*. If sprites A, B, and C all
|
|
511
|
+
overlap each other, bits for A, B, and C are all set, but the register
|
|
512
|
+
gives no information about which pairs actually touched. Games that need
|
|
513
|
+
pair-level resolution must infer it from the bit pattern combined with
|
|
514
|
+
spatial reasoning.
|
|
515
|
+
|
|
516
|
+
### Fix
|
|
517
|
+
|
|
518
|
+
**Establish a single read point per frame.** Read $D01E and $D01F exactly
|
|
519
|
+
once per frame — at the start of the game loop, before any other code can
|
|
520
|
+
inadvertently read them — and immediately store both values in RAM variables.
|
|
521
|
+
All collision processing for that frame operates on the cached values.
|
|
522
|
+
Never read $D01E or $D01F a second time in the same frame.
|
|
523
|
+
|
|
524
|
+
The safest place is at the very start of the frame, in the vertical blank
|
|
525
|
+
handler or as the first action of the main game loop body after the
|
|
526
|
+
`rirq_wait()` call:
|
|
527
|
+
|
|
528
|
+
```kickassembler
|
|
529
|
+
frameStart:
|
|
530
|
+
lda $D01E // read sprite-sprite collision register...
|
|
531
|
+
sta spSprColl // ...and cache it immediately; register is now clear
|
|
532
|
+
lda $D01F // read sprite-background collision register...
|
|
533
|
+
sta spBgColl // ...cache it; register is now clear
|
|
534
|
+
|
|
535
|
+
// All collision tests this frame use spSprColl and spBgColl, not $D01E/$D01F
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**For per-line IRQ-driven collision response:** Enable the collision IRQ via
|
|
539
|
+
$D01A bit 2 (EMMC, sprite-sprite) and bit 1 (EMBC, sprite-background) — the
|
|
540
|
+
same layout as $D019. (An earlier version of this sentence had the two bits
|
|
541
|
+
swapped.) The VIC fires an
|
|
542
|
+
IRQ on the same line the collision is latched. Inside the IRQ handler, read
|
|
543
|
+
$D019 first to identify the interrupt source (bit 2 = sprite-sprite, bit
|
|
544
|
+
1 = sprite-background), then read $D01E or $D01F as appropriate. Acknowledge
|
|
545
|
+
with a write-1-to-clear to the matching bit of $D019. This gives sub-frame
|
|
546
|
+
collision timing but requires careful separation from any main-loop reads.
|
|
547
|
+
|
|
548
|
+
```kickassembler
|
|
549
|
+
// IRQ handler that distinguishes collision type via $D019
|
|
550
|
+
irq_collision:
|
|
551
|
+
pha
|
|
552
|
+
lda $D019 // read VIC interrupt status register
|
|
553
|
+
and #%00000110 // mask: bit 2 = sprite-sprite, bit 1 = sprite-bg
|
|
554
|
+
beq !notCollision+ // if neither bit set, not a collision IRQ
|
|
555
|
+
|
|
556
|
+
tax // save interrupt cause
|
|
557
|
+
lda $D01E
|
|
558
|
+
sta spSprColl // cache sprite-sprite result (clears $D01E)
|
|
559
|
+
lda $D01F
|
|
560
|
+
sta spBgColl // cache sprite-background result (clears $D01F)
|
|
561
|
+
|
|
562
|
+
txa
|
|
563
|
+
sta $D019 // acknowledge: write-1-to-clear the same bits
|
|
564
|
+
|
|
565
|
+
!notCollision:
|
|
566
|
+
pla
|
|
567
|
+
rti
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Do not read $D01E or $D01F in diagnostic or logging code** without
|
|
571
|
+
understanding that every such read is destructive. During development,
|
|
572
|
+
gate diagnostic reads behind a flag or read them only in a context where
|
|
573
|
+
the cached variable is immediately populated.
|
|
574
|
+
|
|
575
|
+
### Cross-references
|
|
576
|
+
|
|
577
|
+
- Register: `D01E` — SPSPCL, sprite-to-sprite collision latch (read-to-clear)
|
|
578
|
+
- Register: `D01F` — SPBGCL, sprite-to-background collision latch (read-to-clear)
|
|
579
|
+
- Register: `D019` — VICIRQ, VIC interrupt status / acknowledge register
|
|
580
|
+
- Technique: `sprite_collision_detect` — the correct polling pattern and
|
|
581
|
+
single-read-per-frame discipline
|
|
582
|
+
- Technique: `stable_raster_irq` — prerequisite for cycle-exact IRQ-driven
|
|
583
|
+
collision response
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## sprite_x_range_hidden_and_seam — A sprite at X below 24 or above 343 sits under the border while the game counts it alive, and on PAL X $1F8 to $1FF is never drawn at all
|
|
588
|
+
|
|
589
|
+
**Severity:** medium
|
|
590
|
+
**Region:** both
|
|
591
|
+
**Triggered by registers:** D000, D010
|
|
592
|
+
**Triggered by techniques:** object_pool, tile_grid_collision, sprite_multiplex_8, sprite_multiplex_24, sideborder_open, multi_sprite_object, flip_screen_rooms, lane_depth_engine, starfield, dypp_sprite_sine_scroller, sprite_border_scroller, dysp_side_border_sprites
|
|
593
|
+
|
|
594
|
+
### Symptom
|
|
595
|
+
|
|
596
|
+
Two faces of one fact.
|
|
597
|
+
|
|
598
|
+
The HUD says three enemies are alive and the player sees two. A shot
|
|
599
|
+
fired at the edge of the screen never lands and never stops. The object
|
|
600
|
+
is there in every table the game keeps, its sprite is enabled, its
|
|
601
|
+
collision bit fires when something walks into it, and nobody can see it,
|
|
602
|
+
because its X is 10, or 350, and the border is drawn over it. A game
|
|
603
|
+
built blind, with the counts checked and the picture not looked at, ships
|
|
604
|
+
this.
|
|
605
|
+
|
|
606
|
+
The other face needs the side borders open. A sprite sliding left off
|
|
607
|
+
the picture, its X stepping down from $1E0 through $1FF and on to $20F
|
|
608
|
+
(which the chip sees as $00F), blinks out for eight steps and comes back.
|
|
609
|
+
On NTSC it does not blink.
|
|
610
|
+
|
|
611
|
+
### Mechanism
|
|
612
|
+
|
|
613
|
+
Sprite X is nine bits, 0 to 511: eight in `$D000 + 2n`, the ninth in
|
|
614
|
+
`$D010`. Nothing in the chip clips that range to the picture. The
|
|
615
|
+
display window is X 24 to 343 with CSEL = 1 (31 to 334 with CSEL = 0),
|
|
616
|
+
and the border is the front-most layer, drawn over any sprite pixel that
|
|
617
|
+
falls outside the window (`mob_priority` in `techniques/sprite.md`). A
|
|
618
|
+
sprite at X 0 to 23 is partly or wholly under the left border; at 321 to
|
|
619
|
+
343 partly and from 344 wholly under the right one. The sprite still
|
|
620
|
+
exists to the VIC-II: its DMA runs every line (the stall in
|
|
621
|
+
`recipes/kickassembler/sideborder-open.md` depends on sprites at X 344
|
|
622
|
+
and 500 fetching on every line) and its position registers read back.
|
|
623
|
+
Only the pixels are missing. Whether `$D01E` still latches a
|
|
624
|
+
sprite-to-sprite hit under the border was not measured here; `$D01F`
|
|
625
|
+
does not, since border pixels are not foreground (`mob_priority`).
|
|
626
|
+
|
|
627
|
+
The seam is a different thing. A PAL line is 63 cycles of 8 pixels, 504
|
|
628
|
+
positions, so the VIC-II's X counter runs 0 to $1F7 and wraps. The nine
|
|
629
|
+
bits can hold $1F8 to $1FF, but the counter never reaches those values,
|
|
630
|
+
so a sprite placed there never matches and is not drawn on any line.
|
|
631
|
+
Measured in VICE x64sc with the side borders open: X 503 ($1F7) draws
|
|
632
|
+
at picture columns 7 to 30, wrapping from the right end of the line to
|
|
633
|
+
the left in the middle of the sprite; X 504, 507 and 511 draw nothing;
|
|
634
|
+
X 0 draws at columns 8 to 31. Position $1F7 and position 0 are
|
|
635
|
+
neighbours, and the eight values between them are a hole. A 16-bit game
|
|
636
|
+
coordinate that steps through them one a frame loses the sprite for
|
|
637
|
+
eight frames; one that steps by 8 or more may skip the hole on one
|
|
638
|
+
frame and fall into it on another.
|
|
639
|
+
|
|
640
|
+
An NTSC line is 65 cycles, 520 positions (arithmetic; the cycle count
|
|
641
|
+
is `hardware/vic-ii-reference.md`'s). Measured in VICE x64sc with
|
|
642
|
+
`-model ntsc` and the borders open: every value from 488 to 511 draws,
|
|
643
|
+
X 511 sits at columns 7 to 30 and X 0 at 8 to 31, so $1FF and 0 are
|
|
644
|
+
neighbours and there is no hole in $1E0 to $20F. Where the line's eight
|
|
645
|
+
positions beyond 512 fall is not visible in the picture and was not
|
|
646
|
+
measured here.
|
|
647
|
+
|
|
648
|
+
The picture VICE writes starts at column 0 = X 496 on PAL and X 504 on
|
|
649
|
+
NTSC, and column 8 = X 0 on both, so a sprite at $1E0 (480) shows its
|
|
650
|
+
last eight columns on PAL (columns 0 to 7) and nothing on NTSC, not
|
|
651
|
+
because it is hidden but because it is left of the picture's edge.
|
|
652
|
+
|
|
653
|
+
### Fix
|
|
654
|
+
|
|
655
|
+
Keep the visibility test in the game, not in the chip. An object is on
|
|
656
|
+
screen only while 24 <= X <= 343 (fully so up to 320); with CSEL = 0,
|
|
657
|
+
31 <= X <= 334. Everything else is off screen for scoring, counting and
|
|
658
|
+
collision, whatever `$D015` says. Either despawn it (free the pool slot,
|
|
659
|
+
so the count and the picture agree) or clamp it (a player sprite stops
|
|
660
|
+
at 24 and 320). For X in the 256 to 343 range the test needs the ninth
|
|
661
|
+
bit; see `sprite_x_high_bit_wrong_register` above for getting it into
|
|
662
|
+
`$D010`.
|
|
663
|
+
|
|
664
|
+
With the side borders open on PAL, never write an X in $1F8 to $1FF.
|
|
665
|
+
Wrap the coordinate at 504, not 512: a sprite leaving to the left at
|
|
666
|
+
$1F7 continues at $1F7 - 504 = -1, that is, at the far right, and a
|
|
667
|
+
table-driven position should be built modulo 504. On NTSC the wrap is
|
|
668
|
+
at 512 within the range measured; a routine that has to run on both
|
|
669
|
+
picks the modulus from the model at start-up.
|
|
670
|
+
|
|
671
|
+
### Worked example
|
|
672
|
+
|
|
673
|
+
The bad pattern is a count over pool slots whose state is not zero,
|
|
674
|
+
with no test on X. The fix is a nine-bit range test and a despawn:
|
|
675
|
+
|
|
676
|
+
```kickassembler
|
|
677
|
+
// objX, objX+1: the 16-bit X the game keeps for one object; objState: 0 = free.
|
|
678
|
+
// The HUD counts every slot whose objState is not 0.
|
|
679
|
+
// Wrong: an object at X 10 or X 400 is alive to that count and under the
|
|
680
|
+
// border to the player. Right: an object counts only while 24 <= X <= 343.
|
|
681
|
+
.label objX = $02
|
|
682
|
+
.label objState = $04
|
|
683
|
+
.const X_LEFT = 24
|
|
684
|
+
.const X_RIGHT = 343
|
|
685
|
+
|
|
686
|
+
on_screen: // returns C = 1 on screen, C = 0 hidden
|
|
687
|
+
lda objX + 1
|
|
688
|
+
bne high_page
|
|
689
|
+
lda objX
|
|
690
|
+
cmp #X_LEFT // 0..23 hidden, 24..255 on screen
|
|
691
|
+
bcs yes
|
|
692
|
+
clc
|
|
693
|
+
rts
|
|
694
|
+
high_page:
|
|
695
|
+
cmp #1
|
|
696
|
+
bne no // 512 and up does not exist on the chip
|
|
697
|
+
lda objX
|
|
698
|
+
cmp #<(X_RIGHT + 1) // 256..343 on screen, 344..511 hidden
|
|
699
|
+
bcc yes
|
|
700
|
+
no:
|
|
701
|
+
clc
|
|
702
|
+
rts
|
|
703
|
+
yes:
|
|
704
|
+
sec
|
|
705
|
+
rts
|
|
706
|
+
|
|
707
|
+
despawn_if_hidden:
|
|
708
|
+
jsr on_screen
|
|
709
|
+
bcs keep
|
|
710
|
+
lda #0
|
|
711
|
+
sta objState // freed: the HUD no longer counts it
|
|
712
|
+
keep:
|
|
713
|
+
rts
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
The measurements behind the numbers above came from one KickAssembler
|
|
717
|
+
program run in VICE x64sc 3.10 (`-default -warp -limitcycles 6000000`,
|
|
718
|
+
PAL and `-model ntsc`), eight solid 24 x 21 sprites in distinct colours
|
|
719
|
+
on a blue background under a black border, X values passed on the
|
|
720
|
+
command line, columns read from the exit screenshot with PIL. Two
|
|
721
|
+
arrangements: borders closed, one sprite per row at eight X values; and
|
|
722
|
+
borders open over a 42-line region by the method of
|
|
723
|
+
`recipes/kickassembler/sideborder-open.md` (all eight sprites Y-expanded
|
|
724
|
+
on the region, a `DEC $D016` whose second write lands on the cycle
|
|
725
|
+
between the two border comparisons, a `YSCROLL` rewrite each line), with
|
|
726
|
+
sprite 0 at the value under test and the other seven parked in the
|
|
727
|
+
window and at X 344. The PAL constants were the recipe's; on NTSC the
|
|
728
|
+
loop opened the border with two more cycles before the first `DEC` and
|
|
729
|
+
one more in the loop, which is a by-product and not a claim about the
|
|
730
|
+
cycle number. Column 8 of the picture is X 0.
|
|
731
|
+
|
|
732
|
+
Borders closed, both models, identical columns:
|
|
733
|
+
|
|
734
|
+
| X | Drawn | Picture columns |
|
|
735
|
+
|---|---|---|
|
|
736
|
+
| 0 | no | none |
|
|
737
|
+
| 23 | 23 of 24 columns | 32 to 54 |
|
|
738
|
+
| 24 | all | 32 to 55 |
|
|
739
|
+
| 320 | all | 328 to 351 |
|
|
740
|
+
| 321 | 23 of 24 | 329 to 351 |
|
|
741
|
+
| 343 | 1 of 24 | 351 |
|
|
742
|
+
| 344 | no | none |
|
|
743
|
+
| 511 | no | none |
|
|
744
|
+
|
|
745
|
+
Borders open, sprite 0 at the seam:
|
|
746
|
+
|
|
747
|
+
| X | PAL columns | NTSC columns |
|
|
748
|
+
|---|---|---|
|
|
749
|
+
| $1E0 (480) | 0 to 7 | none (left of the picture) |
|
|
750
|
+
| $1E8 (488) | not run | 0 to 7 |
|
|
751
|
+
| $1EF (495) | 0 to 22 | 0 to 14 |
|
|
752
|
+
| $1F0 (496) | 0 to 23 | 0 to 15 |
|
|
753
|
+
| $1F4 (500) | 4 to 27 (the side-border recipe's own picture) | 0 to 19 |
|
|
754
|
+
| $1F7 (503) | 7 to 30 | 0 to 22 |
|
|
755
|
+
| $1F8 (504) | none | 0 to 23 |
|
|
756
|
+
| $1FB (507) | none | 3 to 26 |
|
|
757
|
+
| $1FC (508) | not run | 4 to 27 |
|
|
758
|
+
| $1FF (511) | none | 7 to 30 |
|
|
759
|
+
| $200 (0) | 8 to 31 | 8 to 31 |
|
|
760
|
+
| $208 (8) | 16 to 39 | 16 to 39 |
|
|
761
|
+
| $20F (15) | 23 to 46 | 23 to 46 |
|
|
762
|
+
|
|
763
|
+
### Cross-references
|
|
764
|
+
|
|
765
|
+
- Registers: `D000` to `D00E` and `D010`: the nine-bit X the range test
|
|
766
|
+
reads
|
|
767
|
+
- Register: `D016`: CSEL narrows the window to 31 to 334
|
|
768
|
+
- Pitfall: `sprite_x_high_bit_wrong_register`: the ninth bit the test
|
|
769
|
+
above depends on
|
|
770
|
+
- Technique: `sideborder_open`: the only way a sprite at X 0 to 23 or
|
|
771
|
+
344 up is seen, and the only case in which the PAL hole shows
|
|
772
|
+
- Technique: `mob_priority`: the border is drawn over every sprite; a
|
|
773
|
+
sprite under it latches no `$D01F` hit
|
|
774
|
+
- Technique: `object_pool`: the slot state the HUD counts, and where the
|
|
775
|
+
despawn belongs
|
|
776
|
+
- Technique: `tile_grid_collision`: its column is `(x - 24) >> 3`, which
|
|
777
|
+
goes negative for a sprite under the left border
|
|
778
|
+
- Recipe: `recipes/kickassembler/sideborder-open.md`: the region loop
|
|
779
|
+
the measurement reused, with a sprite at X 500 in its picture
|
|
780
|
+
- Recipe: `recipes/kickassembler/sprite-sine-chain.md`: X 343 showing
|
|
781
|
+
one column, X 16 starting at column 32
|
|
782
|
+
|
|
783
|
+
## sprite_registers_persist_across_state_change — A state's setup that writes only the sprite registers it uses inherits the previous state's enables, expansion, multicolour and latched collisions
|
|
784
|
+
|
|
785
|
+
**Severity:** medium
|
|
786
|
+
**Region:** both
|
|
787
|
+
**Triggered by registers:** D015, D01B, D01C, D01D, D01F
|
|
788
|
+
**Triggered by techniques:** attract_mode_input_replay, flip_screen_rooms, two_player_state_swap
|
|
789
|
+
|
|
790
|
+
### Symptom
|
|
791
|
+
|
|
792
|
+
The title screen's pointer, or a piece of its logo, is still sitting on
|
|
793
|
+
the play field after the game starts. The player sprite is twice as wide
|
|
794
|
+
as it should be, or shows in the wrong colours, on the first game after
|
|
795
|
+
the title and looks right after a death. A GAME OVER banner has the
|
|
796
|
+
player sprite parked across it. Or nothing is visibly wrong and the game
|
|
797
|
+
still ends the moment play begins: the collision test fires on the first
|
|
798
|
+
frame from a hit the play state never had. Everything is correct when
|
|
799
|
+
the state is entered from a state that used no sprites, and wrong when
|
|
800
|
+
it is entered from one that did, so the fault seems to move about.
|
|
801
|
+
|
|
802
|
+
### Mechanism
|
|
803
|
+
|
|
804
|
+
The VIC-II keeps every register until something writes it. Nothing about
|
|
805
|
+
a game-state change touches the chip: a screen clear, a new character
|
|
806
|
+
set or a new colour table changes what the sprites are drawn over, not
|
|
807
|
+
whether they are drawn. So each state inherits, from whichever state ran
|
|
808
|
+
before it, the enable mask in `$D015`, the expansions in `$D017` and
|
|
809
|
+
`$D01D`, the multicolour mask in `$D01C`, the priority mask in `$D01B`,
|
|
810
|
+
the X high bits in `$D010`, the shape pointers, the colours in
|
|
811
|
+
`$D027` to `$D02E` and the two shared multicolours in `$D025` and
|
|
812
|
+
`$D026`.
|
|
813
|
+
|
|
814
|
+
A state's setup routine written to set only what the state uses leaves
|
|
815
|
+
all of that standing. The usual shape is a play state that enables its
|
|
816
|
+
player with an `ORA` into `$D015` so as not to disturb anything, sets a
|
|
817
|
+
position and a pointer, and returns. Every other bit of every other
|
|
818
|
+
sprite register is the title screen's. The pointer sprite the title
|
|
819
|
+
enabled stays enabled, at its title position, over the field. The logo's
|
|
820
|
+
X expansion and multicolour bit were set on sprite 0, and the play state
|
|
821
|
+
reuses sprite 0 for the player, so the player is drawn 48 pixels wide
|
|
822
|
+
with its bit pairs read as multicolour, in a colour from `$D025` or
|
|
823
|
+
`$D026` that the play state never set. Nothing goes wrong in the code;
|
|
824
|
+
the chip is doing what it was last told.
|
|
825
|
+
|
|
826
|
+
`$D01F` and `$D01E` make it a logic fault and not only a picture. Both
|
|
827
|
+
are latches: a bit is set when a hit is drawn and stays set until the
|
|
828
|
+
register is read, and the read clears the whole register. A state that
|
|
829
|
+
never reads them leaves any hit it drew latched for the next state, and
|
|
830
|
+
a stale sprite that the next state did not know it had goes on drawing
|
|
831
|
+
hits of its own. The first `LDA $D01F` in the play state then returns a
|
|
832
|
+
non-zero value from a sprite the play state does not own, and a test of
|
|
833
|
+
the form "any hit means the player is dead" ends the game on its first
|
|
834
|
+
frame.
|
|
835
|
+
|
|
836
|
+
The same thing happens in the other direction. A play state that leaves
|
|
837
|
+
its sprites enabled and hands over to a text-only game-over screen has
|
|
838
|
+
the player drawn across the banner, and because the banner is drawn in
|
|
839
|
+
the foreground colour the player collides with it and `$D01F` records a
|
|
840
|
+
hit in a state that has no sprites at all.
|
|
841
|
+
|
|
842
|
+
### Fix
|
|
843
|
+
|
|
844
|
+
Give every state an entry routine, and make the first thing that routine
|
|
845
|
+
does a VIC sprite baseline: write every sprite register to the value
|
|
846
|
+
this state wants, whether or not this state uses it. Write `$D015` first
|
|
847
|
+
with the state's own mask, so that nothing from the old state is drawn
|
|
848
|
+
while the rest is being set, then `$D010`, `$D017`, `$D01B`, `$D01C` and
|
|
849
|
+
`$D01D` as explicit values, then read `$D01E` and `$D01F` once each and
|
|
850
|
+
discard the result to clear the latches. Only then set the state's own
|
|
851
|
+
pointers, positions and colours. A state that uses no sprites calls the
|
|
852
|
+
same routine with a mask of zero. Do not `ORA` and `AND` a state's bits
|
|
853
|
+
into `$D015`; a state owns the whole register while it runs.
|
|
854
|
+
|
|
855
|
+
Sprite colours and the shared multicolours are not in the list because a
|
|
856
|
+
sprite that is not enabled shows none of them, but a state that reuses a
|
|
857
|
+
sprite number must set that sprite's colour, and a state that uses
|
|
858
|
+
multicolour must set `$D025` and `$D026`, for the same reason.
|
|
859
|
+
|
|
860
|
+
If the game reads the collision latches in its per-frame routine, the
|
|
861
|
+
read in the entry routine is still needed: the latch may have been set
|
|
862
|
+
between the old state's last read and the switch.
|
|
863
|
+
|
|
864
|
+
### Worked example
|
|
865
|
+
|
|
866
|
+
The play state on the left of the measurement writes only what it uses.
|
|
867
|
+
The baseline on the right is what the fixed build calls at the top of
|
|
868
|
+
every state's entry routine, with the state's own mask in A.
|
|
869
|
+
|
|
870
|
+
```kickassembler
|
|
871
|
+
// Bad: a play-state setup that touches only the bits it uses.
|
|
872
|
+
// $D015 bits 1 to 7, $D01B, $D01C, $D01D and $D01F are whatever the
|
|
873
|
+
// title screen left.
|
|
874
|
+
play_enter_bad:
|
|
875
|
+
lda #$82
|
|
876
|
+
sta $07f8 // player shape
|
|
877
|
+
lda #160
|
|
878
|
+
sta $d000
|
|
879
|
+
lda #133
|
|
880
|
+
sta $d001
|
|
881
|
+
lda #1
|
|
882
|
+
sta $d027
|
|
883
|
+
lda $d015
|
|
884
|
+
ora #%00000001 // "enable the player", disturb nothing else
|
|
885
|
+
sta $d015
|
|
886
|
+
rts
|
|
887
|
+
|
|
888
|
+
// Fixed: every state entry starts here. A = the sprites this state uses.
|
|
889
|
+
vic_sprite_baseline:
|
|
890
|
+
sta $d015 // this state's own mask first
|
|
891
|
+
lda #0
|
|
892
|
+
sta $d010 // X high bits
|
|
893
|
+
sta $d017 // Y expand
|
|
894
|
+
sta $d01b // priority
|
|
895
|
+
sta $d01c // multicolour
|
|
896
|
+
sta $d01d // X expand
|
|
897
|
+
lda $d01e // read once to clear the latches
|
|
898
|
+
lda $d01f
|
|
899
|
+
rts
|
|
900
|
+
|
|
901
|
+
play_enter_fixed:
|
|
902
|
+
lda #%00000001
|
|
903
|
+
jsr vic_sprite_baseline
|
|
904
|
+
lda #$82
|
|
905
|
+
sta $07f8
|
|
906
|
+
lda #160
|
|
907
|
+
sta $d000
|
|
908
|
+
lda #133
|
|
909
|
+
sta $d001
|
|
910
|
+
lda #1
|
|
911
|
+
sta $d027
|
|
912
|
+
rts
|
|
913
|
+
|
|
914
|
+
over_enter_fixed:
|
|
915
|
+
lda #%00000000 // a text-only state: no sprites at all
|
|
916
|
+
jsr vic_sprite_baseline
|
|
917
|
+
rts
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
The measurement behind this entry was one KickAssembler program run in
|
|
921
|
+
VICE x64sc 3.10, windowless, `-default -warp -limitcycles 8000000`, on
|
|
922
|
+
PAL and with `-model ntsc`. Three states, sixty frames each, switched on
|
|
923
|
+
a frame count: a title with sprite 0 as a logo at (100, 62), X expanded
|
|
924
|
+
and multicolour, and sprite 1 as a pointer bar at (250, 190); a play
|
|
925
|
+
state with a solid character field on rows 13 to 24 and sprite 0 as the
|
|
926
|
+
player at (160, 133), whose bottom line 153 is one line above the
|
|
927
|
+
field's first line 154, so the player itself never touches the field;
|
|
928
|
+
and a text-only game-over state with a banner on row 12. Each state read
|
|
929
|
+
`$D015`, `$D01B`, `$D01C`, `$D01D` and `$D01F` once, in that order, on
|
|
930
|
+
its thirtieth frame, and a monitor tracepoint dumped the copies. The bad
|
|
931
|
+
build's play and game-over entries wrote only what they used; the fixed
|
|
932
|
+
build called the baseline above first. The two models gave the same
|
|
933
|
+
bytes and the same pixel counts throughout.
|
|
934
|
+
|
|
935
|
+
```text
|
|
936
|
+
state bad build fixed build
|
|
937
|
+
15 1B 1C 1D 1F 15 1B 1C 1D 1F
|
|
938
|
+
title 03 00 01 01 00 03 00 01 01 00
|
|
939
|
+
play 03 00 01 01 02 01 00 00 00 00
|
|
940
|
+
game over 03 00 01 01 03 00 00 00 00 00
|
|
941
|
+
|
|
942
|
+
Play state, bad: player 48 px wide, solid red (the title's $D025),
|
|
943
|
+
1,008 sprite pixels; the pointer bar, 84 white
|
|
944
|
+
pixels, standing on the field; $D01F bit 1 is the
|
|
945
|
+
pointer's hit on the field. The play state drew no
|
|
946
|
+
collision of its own.
|
|
947
|
+
Play state, fixed: player 24 px wide in its 1-pixel stripe pattern,
|
|
948
|
+
23 lit columns (the pattern's first is blank), 252
|
|
949
|
+
white pixels; no pointer; $D01F 00.
|
|
950
|
+
Game over, bad: 63 of the banner's 185 white pixels visible, the
|
|
951
|
+
other 122 under 1,008 red sprite pixels (arithmetic);
|
|
952
|
+
the pointer still on screen; $D01F bit 0 is the
|
|
953
|
+
player on the banner, bit 1 the pointer's field hit
|
|
954
|
+
latched after the play state's one read.
|
|
955
|
+
Game over, fixed: 185 white pixels, no sprite pixels, $D01F 00.
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
### Cross-references
|
|
959
|
+
|
|
960
|
+
- Registers: `D015`, `D010`, `D017`, `D01B`, `D01C`, `D01D`: the
|
|
961
|
+
baseline's write list, in the order above
|
|
962
|
+
- Registers: `D01E`, `D01F`: read-to-clear latches; the entry routine's
|
|
963
|
+
read is what stops a hit crossing a state boundary
|
|
964
|
+
- Registers: `D025`, `D026`, `D027` to `D02E`: the colours a state that
|
|
965
|
+
reuses a sprite number must set; the red player above was `$D025`
|
|
966
|
+
- Pitfall: `sprite_priority_collision_silent`: the same latches, read too
|
|
967
|
+
late within one state rather than not at all across two
|
|
968
|
+
- Pitfall: `render_during_state_transition_clobbers_banner` on
|
|
969
|
+
`text-mode-render.md`: the character-mode half of the same transition,
|
|
970
|
+
where the old state's renderer keeps drawing over the new screen
|
|
971
|
+
- Technique: `attract_mode_input_replay`: a title that starts the game
|
|
972
|
+
and returns to itself crosses this boundary twice per cycle
|
|
973
|
+
- Technique: `flip_screen_rooms`: hides the sprites in `$D015` around its
|
|
974
|
+
redraw, which is the correct form of the same write
|
|
975
|
+
- Technique: `two_player_state_swap`: a per-player block swapped on death
|
|
976
|
+
must swap the sprite registers with it
|
|
977
|
+
- Game design: `game_state_machine` in `game-design/game-structure.md`:
|
|
978
|
+
the entry-routine table this baseline belongs at the top of, and the
|
|
979
|
+
acceptance checks that read `$D015` on a state's first frame
|