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,1172 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: render
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- doc-type: pitfall-reference -->
|
|
6
|
+
|
|
7
|
+
# Text-Mode Render Pitfalls
|
|
8
|
+
|
|
9
|
+
Bugs that show up when drawing a game on the text screen: a
|
|
10
|
+
moving-piece overlay on a character playfield (Tetris-likes,
|
|
11
|
+
Sokoban-likes, Boulder Dash, etc.), and the inputs that playfield
|
|
12
|
+
depends on. Some come from over-thinking the rendering layer and one
|
|
13
|
+
from under-budgeting it; the rest are mistakes in what is fed to the
|
|
14
|
+
VIC rather than in the drawing itself: PETSCII bytes where it expects
|
|
15
|
+
screen codes, a colour RAM index that runs past the last cell and into
|
|
16
|
+
the CIA, a colour register compared against the value written to it, a
|
|
17
|
+
mode bit left set from the previous screen, and a charset file embedded
|
|
18
|
+
with its header still on. (An earlier version of this paragraph
|
|
19
|
+
numbered the entries; the numbering went stale twice in a day.)
|
|
20
|
+
An earlier version of this page opened by saying
|
|
21
|
+
a C64 text-mode field redraw is "cheap enough that the simplest 'rewrite
|
|
22
|
+
everything every frame, overlay last' loop just works". It does not: the
|
|
23
|
+
10×20 `render_field` listed below costs about 16,400 cycles (15,500 with
|
|
24
|
+
the display blanked; VICE x64sc PAL, Oscar64 -O2, CIA-timed), and the
|
|
25
|
+
race-free budget is much smaller — `vic_waitBottom` returns at raster
|
|
26
|
+
256 and the display window resumes at 51, 107 lines, ~6,700 cycles — so
|
|
27
|
+
the full repaint runs to about line 205 of the next frame. The shape
|
|
28
|
+
that works is: paint the field on state changes only, and per frame
|
|
29
|
+
erase the piece's previous cells from `field[][]` and draw its current
|
|
30
|
+
ones. The first pitfall below is what goes wrong when the erase is
|
|
31
|
+
skipped; the third is what goes wrong when the whole field is repainted
|
|
32
|
+
instead.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## dirty_cell_skip_leaves_overlay_trail — Skipping unchanged cells leaves the moving piece's prior position un-cleared
|
|
37
|
+
|
|
38
|
+
**Severity:** high
|
|
39
|
+
**Region:** both
|
|
40
|
+
**Triggered by techniques:** text_mode_overlay_render, mixed_sprite_char_actors, bobs_effect, isometric_tile_engine
|
|
41
|
+
|
|
42
|
+
### Symptom
|
|
43
|
+
|
|
44
|
+
A Tetris-like (or any falling-piece / moving-overlay game) draws the
|
|
45
|
+
playfield + an active piece on top. The active piece moves every
|
|
46
|
+
gravity tick, but on screen you see EITHER (a) a smeared trail of every
|
|
47
|
+
position the piece has occupied since spawn, or (b) the piece appears
|
|
48
|
+
to "skip" rows and only becomes visible near the bottom of the field,
|
|
49
|
+
where row activity is highest. Empty rows above appear truly empty
|
|
50
|
+
even though the piece passed through them.
|
|
51
|
+
|
|
52
|
+
The bug is the same in both cases — render_field's "skip cells where
|
|
53
|
+
field[r][c] hasn't changed" optimization is silently dropping the
|
|
54
|
+
writes needed to *clear* the piece's previous position.
|
|
55
|
+
|
|
56
|
+
### Mechanism
|
|
57
|
+
|
|
58
|
+
The piece is rendered as an OVERLAY — its cells aren't in the
|
|
59
|
+
`field[][]` array until the piece locks. The render loop is:
|
|
60
|
+
|
|
61
|
+
```c
|
|
62
|
+
render_field(); // walks field[][], writes screen + color RAM
|
|
63
|
+
render_piece(); // walks active piece's 4 cells, overwrites screen + color RAM
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
When `render_field` repaints every cell unconditionally, render_piece's
|
|
67
|
+
prior-frame writes get overwritten by the (correct) empty-cell content
|
|
68
|
+
of the playfield. The piece moves cleanly. The "trail" from the
|
|
69
|
+
previous frame is implicitly erased.
|
|
70
|
+
|
|
71
|
+
The moment `render_field` adds a "skip if field[r][c] equals last
|
|
72
|
+
frame's field[r][c]" cache, this property collapses:
|
|
73
|
+
|
|
74
|
+
- The piece at frame N-1 is drawn over cells (r, c) where field[r][c] == 0.
|
|
75
|
+
- Frame N: piece moves to (r+1, c). render_field sees field[r][c] is
|
|
76
|
+
still 0, "unchanged" — skips. The piece's prior screen-RAM write
|
|
77
|
+
($A0 + color) survives. render_piece draws the new position.
|
|
78
|
+
- On screen: piece at (r, c) AND (r+1, c). Trail.
|
|
79
|
+
- After 20 gravity ticks: piece smeared across 20 rows.
|
|
80
|
+
|
|
81
|
+
If gravity is fast (level >= 5, or hard-drop) the smear is so dense
|
|
82
|
+
that the eye reads it as "the piece teleported to the bottom and
|
|
83
|
+
appeared with no descent."
|
|
84
|
+
|
|
85
|
+
### Fix
|
|
86
|
+
|
|
87
|
+
The naïve advice "just rewrite the whole field every frame" is WRONG
|
|
88
|
+
on a typical Oscar64 build — see `full_field_redraw_exceeds_vblank`.
|
|
89
|
+
The correct overlay shape is to track the piece's prior position and
|
|
90
|
+
erase + draw only those ~8 cells per frame; `render_field` runs only
|
|
91
|
+
on persistent-state changes (line clear, restart, initial paint).
|
|
92
|
+
|
|
93
|
+
The render order must be: erase prev piece (from field[][] content),
|
|
94
|
+
then draw current piece. Both passes use the piece-cells table — no
|
|
95
|
+
separate dirty-rectangle tracking on the field.
|
|
96
|
+
|
|
97
|
+
```c
|
|
98
|
+
// render_field: unconditional field repaint. Call it on state changes
|
|
99
|
+
// only (line clear, restart, initial paint) — NOT every frame; measured
|
|
100
|
+
// at about 16,400 cycles in Oscar64 -O2, see full_field_redraw_exceeds_vblank.
|
|
101
|
+
// (An earlier version of this comment called it the correct per-frame shape.)
|
|
102
|
+
void render_field(void) {
|
|
103
|
+
for (unsigned char r = 0; r < FIELD_H; r++) {
|
|
104
|
+
unsigned char *srow = Screen + (FIELD_ROW0 + r) * COLS + FIELD_COL0;
|
|
105
|
+
unsigned char *crow = Color + (FIELD_ROW0 + r) * COLS + FIELD_COL0;
|
|
106
|
+
for (unsigned char c = 0; c < FIELD_W; c++) {
|
|
107
|
+
unsigned char cell = field[r][c];
|
|
108
|
+
unsigned char sc = (cell == 0) ? 0x20 : 0xA0;
|
|
109
|
+
unsigned char col = piece_color[cell];
|
|
110
|
+
srow[c*2 + 0] = sc; srow[c*2 + 1] = sc;
|
|
111
|
+
crow[c*2 + 0] = col; crow[c*2 + 1] = col;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
void render_piece(void) {
|
|
117
|
+
// Erase pass first: repaint the prev-position cells from field[][],
|
|
118
|
+
// then draw the current cells (0xA0 + color) — see
|
|
119
|
+
// full_field_redraw_exceeds_vblank for the listing. Do not rely on a
|
|
120
|
+
// per-frame render_field() to erase the prior position.
|
|
121
|
+
...
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Because even the 10×20 field redraw is too slow for the per-frame
|
|
126
|
+
budget on an Oscar64 build, you must EITHER:
|
|
127
|
+
|
|
128
|
+
- Track the piece's prior position separately and explicitly write
|
|
129
|
+
spaces there before drawing the new position (a "dirty rectangle"
|
|
130
|
+
for the overlay, not the field), OR
|
|
131
|
+
- Skip the optimization for the rows the piece currently occupies +
|
|
132
|
+
the rows it occupied last frame.
|
|
133
|
+
|
|
134
|
+
The first approach is cleaner, and it is the one the listing under
|
|
135
|
+
`full_field_redraw_exceeds_vblank` shows. An earlier version of this
|
|
136
|
+
section ended by saying neither was needed on a 10×20 playfield because
|
|
137
|
+
the unconditional rewrite was "well within budget"; it is not — the
|
|
138
|
+
listing above measures about 16,400 cycles a call against a ~6,700-cycle
|
|
139
|
+
race-free window (VICE x64sc, PAL, CIA-timed), which is the third
|
|
140
|
+
pitfall on this page.
|
|
141
|
+
|
|
142
|
+
### Worked example
|
|
143
|
+
|
|
144
|
+
The broken pattern that causes the smear / "only-visible-near-bottom"
|
|
145
|
+
symptom:
|
|
146
|
+
|
|
147
|
+
```c
|
|
148
|
+
// BUGGY: prev_field caches last frame's content.
|
|
149
|
+
static unsigned char prev_field[FIELD_H][FIELD_W];
|
|
150
|
+
|
|
151
|
+
void render_field(void) {
|
|
152
|
+
for (unsigned char r = 0; r < FIELD_H; r++) {
|
|
153
|
+
for (unsigned char c = 0; c < FIELD_W; c++) {
|
|
154
|
+
unsigned char cell = field[r][c];
|
|
155
|
+
if (cell == prev_field[r][c]) continue; // ❌ bug: piece's
|
|
156
|
+
// prior screen write is at a cell where field[r][c] is still
|
|
157
|
+
// 0 and prev_field[r][c] is still 0 — this skip leaves the
|
|
158
|
+
// $A0 from last frame on screen, building a trail.
|
|
159
|
+
prev_field[r][c] = cell;
|
|
160
|
+
// ... write screen + color RAM ...
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Cross-references
|
|
167
|
+
|
|
168
|
+
- Technique: `text_mode_overlay_render` (`docs/techniques/text-mode-render.md`)
|
|
169
|
+
- Related: any game-archetype briefing that proposes a text-mode
|
|
170
|
+
playfield + moving overlay (Tetris, Sokoban, Boulder Dash, board
|
|
171
|
+
games). `c64_game_briefing` with archetype `puzzle` should surface
|
|
172
|
+
this pitfall automatically.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## render_during_state_transition_clobbers_banner — Drawing field/piece after a state change overwrites GAME OVER or line-flash
|
|
177
|
+
|
|
178
|
+
**Severity:** medium
|
|
179
|
+
**Region:** both
|
|
180
|
+
**Triggered by techniques:** text_mode_overlay_render
|
|
181
|
+
|
|
182
|
+
### Symptom
|
|
183
|
+
|
|
184
|
+
A Tetris-like correctly draws a GAME OVER banner (or a line-clear flash
|
|
185
|
+
animation) — for ONE frame. The next frame the banner disappears and
|
|
186
|
+
the playfield reappears as if nothing happened. The state machine has
|
|
187
|
+
correctly flipped to STATE_OVER (or STATE_LINE_FLASH), but the screen
|
|
188
|
+
keeps showing the active game.
|
|
189
|
+
|
|
190
|
+
### Mechanism
|
|
191
|
+
|
|
192
|
+
A typical Tetris main loop looks like:
|
|
193
|
+
|
|
194
|
+
```c
|
|
195
|
+
switch (game_state) {
|
|
196
|
+
case STATE_PLAY: update_play(act); break;
|
|
197
|
+
case STATE_LINE_FLASH: update_line_flash(); break;
|
|
198
|
+
case STATE_OVER: update_game_over(act); break;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
And `update_play` does the gravity tick + renders:
|
|
203
|
+
|
|
204
|
+
```c
|
|
205
|
+
gravity_counter--;
|
|
206
|
+
if (gravity_counter == 0) {
|
|
207
|
+
gravity_tick(); // may flip game_state to OVER / LINE_FLASH
|
|
208
|
+
gravity_counter = ...;
|
|
209
|
+
}
|
|
210
|
+
render_field(); // ❌ runs even if state just changed
|
|
211
|
+
render_piece();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If `gravity_tick` flips `game_state` to STATE_OVER (because the new
|
|
215
|
+
piece collides at spawn) and calls `render_game_over()` to draw the
|
|
216
|
+
banner, the `render_field()` / `render_piece()` calls that follow in
|
|
217
|
+
the *same* iteration of update_play will repaint the playfield
|
|
218
|
+
including the cells the banner just drew. The banner is on screen for
|
|
219
|
+
zero visible frames.
|
|
220
|
+
|
|
221
|
+
### Fix
|
|
222
|
+
|
|
223
|
+
Gate the field+piece render on the current game_state, AFTER the
|
|
224
|
+
gravity tick has had a chance to change it:
|
|
225
|
+
|
|
226
|
+
```c
|
|
227
|
+
if (game_state == STATE_PLAY) {
|
|
228
|
+
render_field();
|
|
229
|
+
render_piece();
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
If the state transitioned, the renderer that handles the new state
|
|
234
|
+
(`render_game_over`, `render_line_flash`) has already drawn what it
|
|
235
|
+
needs, and the next loop iteration will dispatch to that state's
|
|
236
|
+
update function.
|
|
237
|
+
|
|
238
|
+
### Cross-references
|
|
239
|
+
|
|
240
|
+
- Technique: `text_mode_overlay_render`
|
|
241
|
+
- Related pitfall: `dirty_cell_skip_leaves_overlay_trail`
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## full_field_redraw_exceeds_vblank — Per-frame `render_field()` overruns PAL vblank, tearing the upper rows
|
|
246
|
+
|
|
247
|
+
**Severity:** high
|
|
248
|
+
**Region:** both
|
|
249
|
+
**Triggered by techniques:** text_mode_overlay_render, tile_map_render, char_scroll_buffer_v, soft_scroll_v, bitmap_scroll, colour_fade, plasma, text_zoom, speedcode_generation, charset_animation, dycp_scroller, colour_cycling, char_bullets, software_sprite_preshifted, charset_parallax, flip_screen_rooms, creature_state_machine, mixed_sprite_char_actors, eight_way_scroll_double_buffer, tunnel, voxel_landscape, isometric_tile_engine, dot_flag_sine_plotter, fire_effect, twister, shadebobs
|
|
250
|
+
**Mitigated by techniques:** screen_double_buffer_d018
|
|
251
|
+
|
|
252
|
+
### Symptom
|
|
253
|
+
|
|
254
|
+
A Tetris-like (or any per-frame text-mode redraw game) shows the
|
|
255
|
+
piece correctly in the bottom few rows (at most screen rows 20-24) but
|
|
256
|
+
the UPPER rows look empty or stale, even though a memory dump of
|
|
257
|
+
screen RAM shows the piece at the correct upper row. The piece appears to "teleport" into
|
|
258
|
+
the lower screen — you can see it land but never see it descend from
|
|
259
|
+
the top.
|
|
260
|
+
|
|
261
|
+
Confusingly, `readMemory` on screen RAM at any moment shows the piece
|
|
262
|
+
at the expected row. The bug is visual-only and exists because the
|
|
263
|
+
CPU and the VIC raster are racing.
|
|
264
|
+
|
|
265
|
+
### Mechanism
|
|
266
|
+
|
|
267
|
+
The C64 has no frame buffer. The VIC reads screen RAM line by line
|
|
268
|
+
during the visible portion of the raster (PAL lines ~50-249), and the
|
|
269
|
+
CPU can write to screen RAM at any time. After `vic_waitBottom`
|
|
270
|
+
returns at raster 256 there are 107 raster lines — 56 of lower border
|
|
271
|
+
and blanking to the wrap at 311, then 51 of upper border — before the
|
|
272
|
+
display window resumes at raster 51 and the VIC fetches screen RAM
|
|
273
|
+
again: 107 × 63 ≈ **6,700 cycles** in which the CPU can write screen
|
|
274
|
+
RAM without the raster racing it. (The 56 lines to the wrap alone are
|
|
275
|
+
~3,500 cycles, which is what an earlier version of this page called
|
|
276
|
+
"the vblank window"; the race-free budget is nearly twice that.)
|
|
277
|
+
|
|
278
|
+
A naïve full-field redraw — walk every cell of a 10×20 playfield
|
|
279
|
+
(rendered 2-chars-wide = 200 cells × 2 chars), write screen RAM + color
|
|
280
|
+
RAM — costs about 78-86 cycles per cell in Oscar64 -O2 output: the
|
|
281
|
+
listing in the first section measures 15,519 cycles with the display
|
|
282
|
+
blanked and about 16,400 wall-clock started at raster 256 with badline
|
|
283
|
+
stalls counted in (CIA-timed in VICE x64sc), and the recipe's
|
|
284
|
+
`paint_cell` version 17,100-17,450. Call it **15,500-17,500 cycles**
|
|
285
|
+
for 200 cells. (An earlier version of this page said ~92 a cell /
|
|
286
|
+
~18,400; that was an estimate, not a measurement.) The race-free
|
|
287
|
+
window is ~6,700 cycles.
|
|
288
|
+
|
|
289
|
+
When `render_field` is called every frame after `vic_waitBottom`:
|
|
290
|
+
|
|
291
|
+
1. Started at raster 256 (where `vic_waitBottom` returns), the redraw
|
|
292
|
+
runs about 16,400 cycles for the listing in the first section
|
|
293
|
+
(measured in VICE x64sc, PAL, CIA2 timer; 17,100-17,450 for the
|
|
294
|
+
recipe's version) and does not finish until about raster 205-220 of
|
|
295
|
+
the NEXT frame — well past the top of the display window, which
|
|
296
|
+
resumed at line 51. An earlier version of this page said "lines 0 →
|
|
297
|
+
156, ~76 lines past vblank end"; neither number follows from its own
|
|
298
|
+
figures (18,400 / 63 is 292 lines, which from 256 wraps to about
|
|
299
|
+
236) and neither was measured.
|
|
300
|
+
2. The field rows themselves are not what you see go wrong. The loop
|
|
301
|
+
advances about 13 raster lines per field row while the VIC's badlines
|
|
302
|
+
advance 8, so each playfield row is rewritten before the VIC fetches
|
|
303
|
+
it, for every playfield origin from screen row 1 down: rows 0-3 land
|
|
304
|
+
in the previous frame's bottom border, and row r after that at about
|
|
305
|
+
line 13r − 43 against a badline at 51 + 8·(FIELD_ROW0 + r). Measured:
|
|
306
|
+
a cell written by `render_field` is displayed in every one of the 20
|
|
307
|
+
rows. Only the last row is marginal, and only in the slower recipe
|
|
308
|
+
build with FIELD_ROW0 = 2 (its last cells land a line or two after
|
|
309
|
+
the row's badline, so that row can show the field one frame late
|
|
310
|
+
after a lock). An earlier version of this page said the CPU was
|
|
311
|
+
still clearing playfield rows 0..7 after the VIC had drawn them and
|
|
312
|
+
"caught up around row 8 of the playfield (= screen row ~20)" — which
|
|
313
|
+
is also impossible on its face, since a 20-row field on a 25-row
|
|
314
|
+
screen puts playfield row 8 on screen row 13 at the lowest.
|
|
315
|
+
3. What loses the race is the piece overlay. `render_piece` runs only
|
|
316
|
+
after `render_field` returns, at about raster 205-220, when every
|
|
317
|
+
screen row whose badline (51 + 8·row) is earlier than that — rows 0
|
|
318
|
+
to about 19 — has already been fetched for this frame; and the next
|
|
319
|
+
frame's `render_field` erases the piece again before the badline of
|
|
320
|
+
every row it could have reached. So only piece cells on screen rows
|
|
321
|
+
whose fetch falls after the overlay write — screen rows 20-24 at
|
|
322
|
+
most, whose badlines are 211-243, and fewer the longer the overlay
|
|
323
|
+
takes — are ever displayed. With FIELD_ROW0 = 4 that is the bottom
|
|
324
|
+
two to four playfield rows (rows 16-19); with a field ending higher
|
|
325
|
+
on the screen, fewer or none. Every row above that shows the piece
|
|
326
|
+
erased.
|
|
327
|
+
|
|
328
|
+
Net effect: the field content is current everywhere, but the piece is
|
|
329
|
+
invisible in every row above the bottom few; it "appears" only once
|
|
330
|
+
gravity has moved it into the screen rows whose badline falls after
|
|
331
|
+
the overlay write — which is the "teleports into the lower screen"
|
|
332
|
+
symptom.
|
|
333
|
+
|
|
334
|
+
This is a frame-budget bug, not a logic bug. The unconditional redraw
|
|
335
|
+
pattern that "feels safe" because it has no caching, no dirty-cell
|
|
336
|
+
state — is itself the trap.
|
|
337
|
+
|
|
338
|
+
### Fix
|
|
339
|
+
|
|
340
|
+
Stop full-redrawing every frame. Use an overlay pattern with explicit
|
|
341
|
+
prev-piece state:
|
|
342
|
+
|
|
343
|
+
```c
|
|
344
|
+
// In render.c (or wherever the per-frame paint lives):
|
|
345
|
+
|
|
346
|
+
static signed char prev_x, prev_y;
|
|
347
|
+
static unsigned char prev_type, prev_rot;
|
|
348
|
+
static unsigned char have_prev = 0;
|
|
349
|
+
|
|
350
|
+
void render_piece(void) {
|
|
351
|
+
// Pass 1: erase prev cells by reading field[][] (which has the
|
|
352
|
+
// locked piece at those cells if a lock just happened — so this
|
|
353
|
+
// pass correctly redraws a just-locked piece instead of clearing
|
|
354
|
+
// it).
|
|
355
|
+
if (have_prev) {
|
|
356
|
+
for (unsigned char i = 0; i < 4; i++) {
|
|
357
|
+
// ... compute (cx, cy) from prev_type/rot/x/y ...
|
|
358
|
+
unsigned char cell = field[cy][cx];
|
|
359
|
+
unsigned char sc = (cell == 0) ? 0x20 : 0xA0;
|
|
360
|
+
// write screen + color
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Pass 2: draw current piece.
|
|
364
|
+
for (unsigned char i = 0; i < 4; i++) {
|
|
365
|
+
// ... compute (cx, cy) from current_type/rot/x/y ...
|
|
366
|
+
// write 0xA0 + piece_color
|
|
367
|
+
}
|
|
368
|
+
prev_x = current_x; prev_y = current_y;
|
|
369
|
+
prev_type = current_type; prev_rot = current_rot;
|
|
370
|
+
have_prev = 1;
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Per-frame cost drops to 8 cell writes — about **~2,000 cycles** in
|
|
375
|
+
Oscar64 -O2 (`render_piece` measured at 1,998 cycles on CIA 2 timer A,
|
|
376
|
+
~250 a call through `paint_cell`; see
|
|
377
|
+
`recipes/oscar64/text-overlay-playfield.md`), or roughly 150–200 cycles
|
|
378
|
+
in hand assembly with precomputed addresses. Either fits the ~3,500
|
|
379
|
+
cycles to the frame wrap with room to spare, and the 107 raster lines
|
|
380
|
+
(~6,700 cycles) between raster 256 and the display window resuming at
|
|
381
|
+
line 51 are the real race-free budget. An earlier version of this page
|
|
382
|
+
said ~120 cycles, which is 15 a cell against the ~92 it had just priced
|
|
383
|
+
an Oscar64 cell write at. Call `render_field()` only when persistent
|
|
384
|
+
state actually changes:
|
|
385
|
+
|
|
386
|
+
- After a line-clear shift (rare).
|
|
387
|
+
- After game-over restart (rare).
|
|
388
|
+
- Initial paint from `render_init()` (once).
|
|
389
|
+
|
|
390
|
+
Reset `have_prev = 0` whenever `render_field` runs so the next frame
|
|
391
|
+
doesn't try to "erase" cells that have moved.
|
|
392
|
+
|
|
393
|
+
For a brief frame during a line clear, the full redraw will tear —
|
|
394
|
+
that's acceptable because line clears are rare and the tear lasts
|
|
395
|
+
exactly one frame.
|
|
396
|
+
|
|
397
|
+
### Why the naïve advice is wrong
|
|
398
|
+
|
|
399
|
+
This pitfall corrects an earlier version of `text_mode_overlay_render`
|
|
400
|
+
and `dirty_cell_skip_leaves_overlay_trail` that claimed full redraw
|
|
401
|
+
is cheap on C64 ("4800 cycles, 24% of a PAL frame"). That estimate
|
|
402
|
+
under-counted Oscar64's loop overhead and ignored the cost of color
|
|
403
|
+
RAM writes. puzzle-tetris-c64-kb put it at ~18,000 cycles — an
|
|
404
|
+
estimate in the fix commit, never timed; the same listing measures
|
|
405
|
+
15,500-16,400 in VICE and the recipe's version 17,100-17,450 — any of
|
|
406
|
+
which runs through the 107-line race-free window and well into the
|
|
407
|
+
visible draw of the next frame.
|
|
408
|
+
|
|
409
|
+
The takeaway: 200 cell writes IS expensive on a 1 MHz 6502 when
|
|
410
|
+
each "write" is actually 2 STA-absolute + 2 color-RAM STA + indexed
|
|
411
|
+
addressing overhead + loop counter. Don't trust intuitive estimates
|
|
412
|
+
for per-frame text-mode work; measure or budget explicitly.
|
|
413
|
+
|
|
414
|
+
### Cross-references
|
|
415
|
+
|
|
416
|
+
- Technique: `text_mode_overlay_render`
|
|
417
|
+
- Related: `dirty_cell_skip_leaves_overlay_trail` (the bug class you
|
|
418
|
+
hit if you try to fix this by adding a dirty-cell cache to
|
|
419
|
+
`render_field` instead of switching to an overlay pattern).
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## petscii_written_to_screen_ram — PETSCII bytes stored in $0400 show graphics glyphs where the letters should be
|
|
424
|
+
|
|
425
|
+
**Severity:** high
|
|
426
|
+
**Region:** both
|
|
427
|
+
**Triggered by registers:** D018
|
|
428
|
+
**Triggered by techniques:** petscii_screen_code_conversion, text_input_line, decimal_print, password_encoding, high_score_table_insert, adventure_database_engine, text_window_and_menu, two_word_parser
|
|
429
|
+
**Mitigated by techniques:** petscii_screen_code_conversion
|
|
430
|
+
|
|
431
|
+
### Symptom
|
|
432
|
+
|
|
433
|
+
Text written straight into screen RAM comes out wrong in a pattern:
|
|
434
|
+
digits, space and punctuation are right, every letter is a graphics
|
|
435
|
+
character (a `HELLO` written this way shows five box-drawing and
|
|
436
|
+
line glyphs), and text taken from the keyboard with the shift or
|
|
437
|
+
Commodore key held comes out reversed. Sending the same bytes through
|
|
438
|
+
CHROUT shows the right letters. Switching character sets with `$0E`
|
|
439
|
+
or `$8E`, or by writing `$D018`, changes the pictures, and can make the
|
|
440
|
+
unshifted letters look right by accident: in the lower-case set they
|
|
441
|
+
show as upper-case letters, so a program tested after a `$0E` passes
|
|
442
|
+
and breaks when the set changes back. The reversed cells stay reversed
|
|
443
|
+
in both sets. The recipe below has the three rows side by side.
|
|
444
|
+
|
|
445
|
+
### Mechanism
|
|
446
|
+
|
|
447
|
+
PETSCII and screen codes are two different encodings of the same
|
|
448
|
+
glyphs and agree only in `$20-$3F`. The VIC-II fetches a glyph at
|
|
449
|
+
`charset base + 8 * byte` and knows nothing of PETSCII; CHROUT is
|
|
450
|
+
where the translation lives, in the KERNAL's screen editor at `$E716`,
|
|
451
|
+
and a store to `$0400` bypasses it. A letter is PETSCII `$41-$5A` but
|
|
452
|
+
screen code `$01-$1A`, so the byte `$48` (`H`) selects glyph `$48`.
|
|
453
|
+
In the upper-case/graphics set that glyph is a vertical bar; in the
|
|
454
|
+
lower-case set glyphs `$41-$5A` are the upper-case letters, byte for
|
|
455
|
+
byte the same bitmaps as `$01-$1A` in the first set (read from the
|
|
456
|
+
`chargen-901225-01.bin` image), so the same wrong byte shows the right
|
|
457
|
+
letter. Shifted PETSCII is `$C1-$DA` with bit 7 set, and bit 7 of a
|
|
458
|
+
screen code is reverse video, so those cells come out reversed in
|
|
459
|
+
either set, and Commodore-key graphics (`$A0-$BF`) likewise. `$D018`
|
|
460
|
+
bit 1 picks which 2 KB of the character ROM supplies the pictures,
|
|
461
|
+
which is why changing it changes the glyphs without touching the bytes
|
|
462
|
+
(measured in VICE x64sc 3.10: CHROUT `$0E` sets `$D018` to `$17`, `$8E`
|
|
463
|
+
back to `$15`, and the bytes in screen RAM are the same before and
|
|
464
|
+
after).
|
|
465
|
+
|
|
466
|
+
The same mistake in the other direction is quieter: screen codes
|
|
467
|
+
`$01-$1A` are PETSCII control codes, so a name read back from `$0400`
|
|
468
|
+
and handed to CHROUT loses its letters and keeps only its digits,
|
|
469
|
+
spaces and punctuation. Most of those codes print nothing and leave
|
|
470
|
+
the cursor column where it was; some change the colour or move the
|
|
471
|
+
cursor (measured in VICE x64sc 3.10: CHROUT of `$01-$04` after a PLOT
|
|
472
|
+
to column 0 leaves `$0400-$0403` at zero and the column at 0).
|
|
473
|
+
|
|
474
|
+
### Fix
|
|
475
|
+
|
|
476
|
+
Convert at the boundary, every time a byte crosses from a PETSCII
|
|
477
|
+
source (keyboard, file, CHROUT-style string) to screen RAM or back.
|
|
478
|
+
The rule is six compares and a mask (`petscii_screen_code_conversion`
|
|
479
|
+
in `techniques/text.md`): `$20-$3F` unchanged, `$40-$5F` less `$40`,
|
|
480
|
+
`$60-$7F` less `$20`, `$A0-$FF` low seven bits with bit 6 set, `$FF`
|
|
481
|
+
to `$5E`. Or write text through CHROUT and let the KERNAL convert,
|
|
482
|
+
accepting the cursor, the wrap and the clobbered registers. In
|
|
483
|
+
KickAssembler, `.text` already emits screen codes; the trap there is
|
|
484
|
+
the other way round, a `.text` string sent through CHROUT
|
|
485
|
+
(`toolchains/kickassembler-reference.md`). Keep stored text in one encoding,
|
|
486
|
+
PETSCII by preference, and convert on the way to the screen.
|
|
487
|
+
|
|
488
|
+
### Worked example
|
|
489
|
+
|
|
490
|
+
```c
|
|
491
|
+
// Bad: PETSCII bytes poked into screen RAM; every letter is a graphics glyph.
|
|
492
|
+
const char msg[] = { 0x48, 0x45, 0x4c, 0x4c, 0x4f, 0 }; // HELLO in PETSCII
|
|
493
|
+
for (char i = 0; msg[i]; i++) SCREEN[40 * 4 + i] = msg[i];
|
|
494
|
+
|
|
495
|
+
// Good: convert on the way in; digits and space pass through unchanged.
|
|
496
|
+
for (char i = 0; msg[i]; i++) SCREEN[40 * 3 + i] = pet2scr(msg[i]);
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
`pet2scr()` is the routine in `recipes/oscar64/petscii-screen-codes.md`,
|
|
500
|
+
whose screenshot shows both rows.
|
|
501
|
+
|
|
502
|
+
### Cross-references
|
|
503
|
+
|
|
504
|
+
- Technique: `petscii_screen_code_conversion` (the rule, measured
|
|
505
|
+
against CHROUT over every code)
|
|
506
|
+
- Techniques that write screen RAM from a PETSCII source:
|
|
507
|
+
`text_input_line`, `decimal_print`
|
|
508
|
+
- Register: `$D018` bit 1 (`hardware/vic-ii-reference.md`) selects the
|
|
509
|
+
set; it never changes the code
|
|
510
|
+
- Recipe: `recipes/oscar64/petscii-screen-codes.md`
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## colour_ram_index_past_last_cell_hits_cia1 — A colour RAM index of 1,024 or more writes CIA1's registers, not colour RAM
|
|
515
|
+
|
|
516
|
+
**Severity:** high
|
|
517
|
+
**Region:** both
|
|
518
|
+
**Triggered by registers:** DC00, DC02, DC04, DC0D, DC0E
|
|
519
|
+
**Triggered by techniques:** tile_map_render, colour_cycling, colour_fade, screen_wipe, text_window_and_menu, text_mode_overlay_render, flip_screen_rooms, plasma, koala_format, char_scroll_buffer_h, difficulty_ramp_tables, fire_effect, screen_dissolve_lfsr, shadebobs
|
|
520
|
+
|
|
521
|
+
### Symptom
|
|
522
|
+
|
|
523
|
+
A colour fill or colour copy runs, the screen looks right, and
|
|
524
|
+
something unrelated stops. Which thing depends on how far the index
|
|
525
|
+
went and what value it carried. The jiffy clock freezes, the cursor
|
|
526
|
+
stops flashing and the keyboard goes dead; or the keyboard alone goes
|
|
527
|
+
dead while the joystick still reads; or a CIA-timed measurement returns
|
|
528
|
+
nonsense while the frame count stays plausible (that last is how the
|
|
529
|
+
`recipes/oscar64/difficulty-tables.md` build met it: coins drawn on text
|
|
530
|
+
rows up to 28 put the colour writes at `$D800 + 1120` and beyond). No
|
|
531
|
+
cell on the screen is wrong, because every cell was written before the
|
|
532
|
+
index left the chip.
|
|
533
|
+
|
|
534
|
+
### Mechanism
|
|
535
|
+
|
|
536
|
+
The screen is 40 by 25, which is 1,000 cells, and colour RAM holds
|
|
537
|
+
them at `$D800` to `$DBE7`. The page it sits in, `$D800` to `$DBFF`, is
|
|
538
|
+
1,024 bytes long (arithmetic), so a fill whose 16-bit index is compared
|
|
539
|
+
against a page boundary, or a copy that moves four pages of 256 because
|
|
540
|
+
1,000 does not divide, writes 24 bytes past the last cell into the
|
|
541
|
+
spare colour RAM at `$DBE8` to `$DBFF`, which is harmless, and then
|
|
542
|
+
byte 1,024 lands at `$DC00`: CIA1. The next sixteen indices walk the
|
|
543
|
+
chip's sixteen registers in order, port A, port B, the two data
|
|
544
|
+
direction registers, timer A, timer B, the four time-of-day registers,
|
|
545
|
+
the serial register, the interrupt control register and the two control
|
|
546
|
+
registers. The registers repeat every 16 bytes to `$DCFF` (index 1,279),
|
|
547
|
+
so a longer overrun writes each of them again on every pass, and index
|
|
548
|
+
1,280 reaches CIA2.
|
|
549
|
+
|
|
550
|
+
What a single pass does was measured in VICE x64sc 3.10 (PAL) with a
|
|
551
|
+
fill of `$0E`, light blue, whose 16-bit index ran to 1,040. CIA1 before,
|
|
552
|
+
`$DC00` to `$DC0F`:
|
|
553
|
+
|
|
554
|
+
```text
|
|
555
|
+
7f ff ff 00 93 24 ff ff 00 00 00 01 00 00 01 08
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
After the fill:
|
|
559
|
+
|
|
560
|
+
```text
|
|
561
|
+
ff 7f 0e 0e 4e 21 0e 0e 0e 0e 0e 01 0e 00 0e 0e
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Every register that reads back its written value now reads `$0E`.
|
|
565
|
+
Timer A read `$214E` on two samples about 600 cycles apart, where before
|
|
566
|
+
the fill the same two samples read `$2448` then `$2243`: the `$0E`
|
|
567
|
+
written to `$DC0E` has bit 0 clear, so timer A is stopped, and the `$0E`
|
|
568
|
+
written to `$DC0D` cleared the interrupt masks for timers B, the alarm
|
|
569
|
+
and the serial port. The KERNAL's jiffy clock at `$A2` advanced from
|
|
570
|
+
`$38` to `$4D` over a delay loop run before the fill and stayed at `$50`
|
|
571
|
+
over the same loop run after it. With no jiffy interrupt there is no
|
|
572
|
+
SCNKEY, so no key is read. `$DC02`, port A's data direction register,
|
|
573
|
+
reads `$0E`: only bits 1 to 3 drive, and a keyboard scan that then
|
|
574
|
+
selects every column by writing `$00` to `$DC00` reads `$F1` back from
|
|
575
|
+
the port, not `$00`. Bits 6 and 7 of `$DC01` now carry the timer outputs
|
|
576
|
+
(`$7F` after this run, `$3F` after an identical one), because the `$0E`
|
|
577
|
+
in both control registers set the port-B-on bit. The same fill with the
|
|
578
|
+
index stopped at 1,000 left all sixteen bytes as they were, timer A
|
|
579
|
+
counting (`$27C7` then `$256C`), and the column select reading `$00` from
|
|
580
|
+
`$DC00` and `$FF` from `$DC01`.
|
|
581
|
+
|
|
582
|
+
The most common fill value is `$00`, black, and it does its damage at
|
|
583
|
+
index 1,026: `$00` into `$DC02` turns every port A line into an input,
|
|
584
|
+
which is the state `cia1_ddr_cleared_kills_keyboard` in
|
|
585
|
+
`pitfalls/input.md` describes. Measured with a `$00` fill whose index
|
|
586
|
+
ran to 1,027: `$DC02` read `$00` after it, the timers were still running
|
|
587
|
+
(`$228A` then `$205A`), and the column select read `$FF` from `$DC00`
|
|
588
|
+
whatever was written to it, so the scanner's column drive is gone and the
|
|
589
|
+
keyboard is dead while the joystick, which grounds its lines, still
|
|
590
|
+
reads. A fill that runs to 1,136 rewrites the sixteen registers seven
|
|
591
|
+
times over; after it timer A read `$0E0E` on both samples, stopped and
|
|
592
|
+
reloaded from the latch the fill wrote (measured, same build).
|
|
593
|
+
|
|
594
|
+
The 24 spare bytes are real nibble RAM. After the `$0E` fill they read
|
|
595
|
+
`$0E` from `$DBE8` to `$DBFF`; after the `$00` fill they read `$00` in
|
|
596
|
+
21 places and `$F0` in three, the low nibble the value written and the
|
|
597
|
+
high nibble whatever the VIC last fetched, which changes from read to
|
|
598
|
+
read. With the index stopped at 1,000 they held what the machine came up
|
|
599
|
+
with (`02 0f f8 07 00 0f 04 0f 04 0f 05 0f 03 0e 00 0b`; a second boot
|
|
600
|
+
gave the same sixteen low nibbles under different high ones), because
|
|
601
|
+
the KERNAL's screen clear stops at 1,000 as well. Mask reads of this region
|
|
602
|
+
with `AND #$0F`; `hardware/c64-registers-reference.md` says the same.
|
|
603
|
+
|
|
604
|
+
### Fix
|
|
605
|
+
|
|
606
|
+
Bound the index at 1,000, not at a page boundary. A fill by rows, 25
|
|
607
|
+
rows of 40 with an 8-bit loop inside a row, cannot pass the last cell.
|
|
608
|
+
A fill that must be a single 16-bit loop compares the low byte against
|
|
609
|
+
`<1000` and the high byte against `>1000`, or counts four pages and
|
|
610
|
+
stops the last one at `$E8`. In a compiler, put the size in the type
|
|
611
|
+
(`char colour[25][40]`) or assert it (`static_assert(sizeof(map) ==
|
|
612
|
+
1000)` in Oscar64), and clamp any row index before it multiplies by 40:
|
|
613
|
+
row 25 is already the spare bytes, row 25 cell 24 onward is the CIA. If
|
|
614
|
+
a fill is allowed to run over on purpose, to 1,024 for a fast unrolled
|
|
615
|
+
copy, it must stop at 1,024 exactly, and the 24 bytes it writes past the
|
|
616
|
+
screen must not be counted on to hold a full byte.
|
|
617
|
+
|
|
618
|
+
### Worked example
|
|
619
|
+
|
|
620
|
+
Bad: a "fill four pages" loop, the shape a screen clear takes when the
|
|
621
|
+
programmer rounds 1,000 up. It writes `$DC00` to `$DC17` on its last
|
|
622
|
+
page.
|
|
623
|
+
|
|
624
|
+
```asm
|
|
625
|
+
// BAD: 4 x 256 = 1,024, and cell 1,000 onward is not colour RAM.
|
|
626
|
+
fill_colour_bad:
|
|
627
|
+
lda #$0e
|
|
628
|
+
ldx #$00
|
|
629
|
+
!: sta $d800,x
|
|
630
|
+
sta $d900,x
|
|
631
|
+
sta $da00,x
|
|
632
|
+
sta $db00,x // stops at $dbff: the 24 spare nibbles, no harm
|
|
633
|
+
inx
|
|
634
|
+
bne !-
|
|
635
|
+
rts
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
That form stops at `$DBFF` and is harmless. The one that reaches the
|
|
639
|
+
CIA is its 16-bit cousin, a pointer walked until the high byte turns
|
|
640
|
+
over, which is what the difficulty-tables build and the measured fill
|
|
641
|
+
above both did:
|
|
642
|
+
|
|
643
|
+
```asm
|
|
644
|
+
// BAD: meant to stop when the high byte reaches $dc; the compare is
|
|
645
|
+
// one page late, so all of $dc00-$dcff is written, CIA1 sixteen times.
|
|
646
|
+
fill_colour_bad16:
|
|
647
|
+
lda #<$d800
|
|
648
|
+
sta $fd
|
|
649
|
+
lda #>$d800
|
|
650
|
+
sta $fe
|
|
651
|
+
ldy #$00
|
|
652
|
+
lda #$0e
|
|
653
|
+
!: sta ($fd),y
|
|
654
|
+
iny
|
|
655
|
+
bne !-
|
|
656
|
+
inc $fe
|
|
657
|
+
lda $fe
|
|
658
|
+
cmp #$dd // should be #$dc
|
|
659
|
+
bne !-
|
|
660
|
+
rts
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Good: 25 rows of 40, and no index that can leave the chip. Run under
|
|
664
|
+
the same harness, this routine left all sixteen CIA1 bytes as the boot
|
|
665
|
+
had them (timer A still counting), wrote `$0E` to cell 0 and to cell
|
|
666
|
+
999 at `$DBE7`, and left `$DBE8` onward as it found it (measured, VICE
|
|
667
|
+
x64sc 3.10).
|
|
668
|
+
|
|
669
|
+
```asm
|
|
670
|
+
// GOOD: rows of 40, an 8-bit index inside each row.
|
|
671
|
+
fill_colour_good:
|
|
672
|
+
lda #<$d800
|
|
673
|
+
sta $fd
|
|
674
|
+
lda #>$d800
|
|
675
|
+
sta $fe
|
|
676
|
+
ldx #25
|
|
677
|
+
lda #$0e
|
|
678
|
+
!row: ldy #39
|
|
679
|
+
!: sta ($fd),y
|
|
680
|
+
dey
|
|
681
|
+
bpl !-
|
|
682
|
+
pha
|
|
683
|
+
clc
|
|
684
|
+
lda $fd
|
|
685
|
+
adc #40
|
|
686
|
+
sta $fd
|
|
687
|
+
bcc !+
|
|
688
|
+
inc $fe
|
|
689
|
+
!: pla
|
|
690
|
+
dex
|
|
691
|
+
bne !row-
|
|
692
|
+
rts
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### Cross-references
|
|
696
|
+
|
|
697
|
+
- **Sibling pitfall:** `cia1_ddr_cleared_kills_keyboard` in
|
|
698
|
+
`pitfalls/input.md` is the state a `$00` fill leaves at index 1,026,
|
|
699
|
+
reached there by a deliberate write; this entry is the same state
|
|
700
|
+
reached by an index.
|
|
701
|
+
- **Hardware:** `hardware/c64-registers-reference.md`, the Color RAM
|
|
702
|
+
section, for `$DBE8` to `$DBFF` (nibble RAM, high nibble undefined) and
|
|
703
|
+
the I/O map that puts CIA1 at `$DC00`; `hardware/cia-reference.md` for
|
|
704
|
+
what each of the sixteen registers does with the byte it is given.
|
|
705
|
+
- **Recipe:** `recipes/oscar64/difficulty-tables.md`, "A bug this page
|
|
706
|
+
had": the overrun met in the wild, at `$D800 + 1120`.
|
|
707
|
+
- **Not measured here:** NTSC (the mechanism is address arithmetic and
|
|
708
|
+
has no region term); a real key press after the overrun (the harness
|
|
709
|
+
cannot press one; the keyboard consequence is read from the port bytes
|
|
710
|
+
and from `cia1_ddr_cleared_kills_keyboard`); the CIA2 case at index
|
|
711
|
+
1,280.
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## vic_colour_register_upper_nibble_reads_set — A VIC colour register reads back as the colour plus 240, so a compare against the value written never matches
|
|
716
|
+
|
|
717
|
+
**Severity:** medium
|
|
718
|
+
**Region:** both
|
|
719
|
+
**Triggered by registers:** D020, D021, D022, D023, D024, D025, D026, D027, D028, D029, D02A, D02B, D02C, D02D, D02E, D016, D018, D019, D01A
|
|
720
|
+
**Triggered by techniques:** basic_extension_wedge
|
|
721
|
+
|
|
722
|
+
### Symptom
|
|
723
|
+
|
|
724
|
+
An `IF PEEK(53280)=2` that is silently false after `POKE 53280,2`. A
|
|
725
|
+
`cmp #2` after `lda $d020` whose branch is never taken. A colour saved
|
|
726
|
+
with `lda $d021` and used as a table index that reads 240 bytes past the
|
|
727
|
+
end of a sixteen-entry table. A "restore the border" routine that works,
|
|
728
|
+
because a write only takes the low four bits, while the compare in the
|
|
729
|
+
same program does not. Nothing crashes and nothing is drawn wrong; the
|
|
730
|
+
program just takes the other branch, and the bug looks like logic.
|
|
731
|
+
|
|
732
|
+
This bit the BASIC wedge recipe on its first run: `&B 2` set the border
|
|
733
|
+
red, `IF PEEK(53280)=2 THEN` skipped its line without a word, and the
|
|
734
|
+
verdict byte was never written.
|
|
735
|
+
|
|
736
|
+
### Mechanism
|
|
737
|
+
|
|
738
|
+
The VIC-II has four bits of storage behind each colour register
|
|
739
|
+
($D020 to $D02E). A write keeps the low nibble and drops the rest. A
|
|
740
|
+
read drives the low four bits from that storage and leaves the upper
|
|
741
|
+
four data lines undriven, and on this chip an undriven line reads as 1.
|
|
742
|
+
So every colour register reads back as the colour plus $F0 (240). VICE
|
|
743
|
+
models that as ones as well; measured below on both models, and the
|
|
744
|
+
audited `hardware/vic-ii-reference.md` says the same ("Only the low 4
|
|
745
|
+
bits matter; the upper 4 bits read as 1" under $D020, "Bits 7-4 read 1"
|
|
746
|
+
under $D021).
|
|
747
|
+
|
|
748
|
+
The same thing happens to every other unused VIC bit. Measured in the
|
|
749
|
+
same run: $D016 written $00 reads $C0 (bits 7-6), $D018 written $14
|
|
750
|
+
reads $15 (bit 0), $D019 reads with bits 6-4 set, and $D01A written $00
|
|
751
|
+
reads $F0 (bits 7-4). The hardware page marks each of those bits "read 1"
|
|
752
|
+
and agrees with the measurement. $D011 has no unused bit and read back
|
|
753
|
+
exactly what was written ($1B). $D01E and $D01F are eight full bits of
|
|
754
|
+
collision latch: both read $00 with no sprites on, and no bit reads set
|
|
755
|
+
there, so this pitfall does not touch them (their own trap is that the
|
|
756
|
+
read clears them, `sprite_priority_collision_silent` in
|
|
757
|
+
`pitfalls/sprite.md`). $D02F to $D03F have no register at all and read
|
|
758
|
+
$FF. Whether the upper nibble reads as 1 on every real VIC revision is
|
|
759
|
+
not measured here; VICE, both models, is what the table below shows.
|
|
760
|
+
|
|
761
|
+
### Fix
|
|
762
|
+
|
|
763
|
+
Mask before you compare: `and #$0f` after the read, or `(PEEK(53280)
|
|
764
|
+
AND 15)` in BASIC. If a colour is going to index a table, mask it first.
|
|
765
|
+
Better, keep your own copy of each colour in RAM and never read the
|
|
766
|
+
register back; the register is write-only in effect, and a shadow byte
|
|
767
|
+
also survives a raster routine that changes the border mid-frame. A read
|
|
768
|
+
of a VIC colour register that is stored, compared or indexed without a
|
|
769
|
+
mask is wrong; a read that is written straight back to a colour register
|
|
770
|
+
is harmless, because the write drops the nibble again.
|
|
771
|
+
|
|
772
|
+
### Worked example
|
|
773
|
+
|
|
774
|
+
The fragment builds as written. The first compare is the bug; the
|
|
775
|
+
second is the fix.
|
|
776
|
+
|
|
777
|
+
```asm
|
|
778
|
+
// Bad: after a write of 2, $D020 reads $F2. Z is never set here.
|
|
779
|
+
lda #2
|
|
780
|
+
sta $d020
|
|
781
|
+
lda $d020
|
|
782
|
+
cmp #2
|
|
783
|
+
beq bad_match // never taken
|
|
784
|
+
jmp keep_going
|
|
785
|
+
bad_match:
|
|
786
|
+
inc $0400 // would show a glyph; it never does
|
|
787
|
+
keep_going:
|
|
788
|
+
|
|
789
|
+
// Good: mask the read, then compare the low nibble.
|
|
790
|
+
lda $d020
|
|
791
|
+
and #$0f
|
|
792
|
+
cmp #2
|
|
793
|
+
beq good_match // taken
|
|
794
|
+
jmp done
|
|
795
|
+
good_match:
|
|
796
|
+
inc $0401 // the glyph appears
|
|
797
|
+
done:
|
|
798
|
+
rts
|
|
799
|
+
|
|
800
|
+
// Better: a shadow byte, written once with the colour, is always exact.
|
|
801
|
+
border_shadow:
|
|
802
|
+
.byte 2
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
The read-back measured in VICE 3.10 x64sc, `-model ntsc` and default
|
|
806
|
+
PAL, with the program above's write-and-read loop over every value.
|
|
807
|
+
Each register was written 0 to 15 and read straight back; the two
|
|
808
|
+
models gave identical bytes:
|
|
809
|
+
|
|
810
|
+
```text
|
|
811
|
+
register written 0..15 reads back as
|
|
812
|
+
$D020-$D02E 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
|
|
813
|
+
(all fifteen registers, PAL and NTSC; written value + 240)
|
|
814
|
+
$D020 <- 2, read, AND #$0F -> 2
|
|
815
|
+
$D016 <- $08 / $C8 / $00, read -> $C8 / $C8 / $C0 (bits 7-6 read 1)
|
|
816
|
+
$D018 <- $15 / $14, read -> $15 / $15 (bit 0 reads 1)
|
|
817
|
+
$D019 read -> $71 PAL, $70 NTSC (bits 6-4 read 1;
|
|
818
|
+
bit 0 is the raster latch, not an unused bit)
|
|
819
|
+
$D01A <- $00 / $0F / $01, read -> $F0 / $FF / $F1 (bits 7-4 read 1)
|
|
820
|
+
$D011 <- $1B, read -> $1B (no unused bit)
|
|
821
|
+
$D01E, $D01F read, sprites off -> $00, $00 (no unused bit)
|
|
822
|
+
$D02F, $D03F read -> $FF, $FF (no register)
|
|
823
|
+
|
|
824
|
+
BASIC, typed into the KERNAL buffer, PAL and NTSC:
|
|
825
|
+
POKE53280,2:PRINTPEEK(53280);PEEK(53280)AND15 -> 242 2
|
|
826
|
+
IF PEEK(53280)=2 THEN PRINT "EQ2" -> (nothing printed)
|
|
827
|
+
IF PEEK(53280)=242 THEN PRINT "EQ242" -> EQ242
|
|
828
|
+
POKE53281,3:PRINTPEEK(53281);PEEK(53281)AND15 -> 243 3
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
### Cross-references
|
|
832
|
+
|
|
833
|
+
- Technique: `basic_extension_wedge` (`techniques/text.md`), whose
|
|
834
|
+
recipe met this and masks with `AND 15`
|
|
835
|
+
- Recipe: `recipes/kickassembler/basic-wedge.md`, the run that found it
|
|
836
|
+
- Registers: `$D020` to `$D02E`, `$D016`, `$D018`, `$D019`, `$D01A`
|
|
837
|
+
(`hardware/vic-ii-reference.md`; each entry marks the bits that read 1)
|
|
838
|
+
- Pitfall: `d016_unmasked_rmw_clobbers_csel_mcm` (`pitfalls/scroll.md`),
|
|
839
|
+
the read-modify-write face of the same two bits on `$D016`
|
|
840
|
+
- Pitfall: `sprite_priority_collision_silent` (`pitfalls/sprite.md`),
|
|
841
|
+
the read trap on `$D01E`/`$D01F`, which is clearing, not garbage
|
|
842
|
+
- Pitfall: `sid_write_only_registers` (`pitfalls/sid.md`), the
|
|
843
|
+
neighbouring chip, where the whole byte is garbage rather than one
|
|
844
|
+
nibble
|
|
845
|
+
|
|
846
|
+
## ecm_with_mcm_set_is_invalid_black_mode — Setting ECM while MCM is still on selects an invalid mode that draws the whole window black
|
|
847
|
+
|
|
848
|
+
**Severity:** medium
|
|
849
|
+
**Region:** both
|
|
850
|
+
**Triggered by registers:** D011, D016
|
|
851
|
+
**Triggered by techniques:** ecm_mode, mcm_text, raster_split_modes
|
|
852
|
+
|
|
853
|
+
### Symptom
|
|
854
|
+
|
|
855
|
+
The program switches a text screen to Extended Colour Mode and the
|
|
856
|
+
display window goes black. Not blank: black. The border keeps its
|
|
857
|
+
colour, the raster interrupts keep firing, and sprites still show, so
|
|
858
|
+
the machine is plainly running. The four background bands the code set
|
|
859
|
+
up are not there, and neither are the glyphs. Worse, the game keeps
|
|
860
|
+
playing against the field nobody can see: the sprite-to-background
|
|
861
|
+
collision bit still sets when a sprite crosses a glyph, and a sprite
|
|
862
|
+
set to run behind the playfield is still cut by the glyph pixels, which
|
|
863
|
+
are now the same black as everything round them.
|
|
864
|
+
|
|
865
|
+
The usual route in is a mode change. The previous screen was a
|
|
866
|
+
multicolour character screen, so `$D016` still holds `$D8` with MCM
|
|
867
|
+
(bit 4) set. The ECM screen's setup writes `$D011` with bit 6 and never
|
|
868
|
+
touches `$D016`, because ECM is a `$D011` mode and the writer's mental
|
|
869
|
+
model of `$D016` is "scroll and 38-column". ECM and MCM are now both
|
|
870
|
+
set, and that pair is not a mode.
|
|
871
|
+
|
|
872
|
+
### Mechanism
|
|
873
|
+
|
|
874
|
+
The VIC-II decodes its display mode from three bits: ECM (`$D011` bit
|
|
875
|
+
6), BMM (`$D011` bit 5) and MCM (`$D016` bit 4). Five of the eight
|
|
876
|
+
combinations are modes. The other three, every combination in which ECM
|
|
877
|
+
is set alongside BMM or MCM, are the invalid modes: the audited
|
|
878
|
+
`hardware/vic-ii-reference.md` lists ECM+MCM text, ECM+BMM and
|
|
879
|
+
ECM+BMM+MCM as "output is black", and says that in those modes "the
|
|
880
|
+
display sequencer still runs, but the pixel data output is forced to
|
|
881
|
+
black. Collisions and sprites still function."
|
|
882
|
+
|
|
883
|
+
That last sentence is the trap. The sequencer still classifies each
|
|
884
|
+
pixel as foreground or background, and the sprite unit still compares
|
|
885
|
+
against that classification for `$D01F` and for the `$D01B` priority
|
|
886
|
+
mask; only the colour lookup is replaced by black. So the picture
|
|
887
|
+
disappears while every part of the logic that reads the picture through
|
|
888
|
+
the VIC carries on as if it were there.
|
|
889
|
+
|
|
890
|
+
Measured in VICE 3.10 x64sc, PAL and NTSC, with a screen of four
|
|
891
|
+
character bands (codes 1, 65, 129, 193), white colour RAM, `$D021` to
|
|
892
|
+
`$D024` set to blue, green, red and yellow, a light blue border and one
|
|
893
|
+
solid white sprite parked over the second band. Setting `$D011` bit 6
|
|
894
|
+
with `$D016` at `$C8` gave the four coloured bands with white glyphs on
|
|
895
|
+
both models. Setting the same bit with `$D016` at `$D8` gave a window of
|
|
896
|
+
63,496 black pixels and 504 white ones, and the 504 are the 24 by 21
|
|
897
|
+
sprite. The border stayed light blue. `$D01F` read `$01` two frames
|
|
898
|
+
after the mode write and `$01` again a frame later, on both models, in
|
|
899
|
+
the black mode exactly as in the good one. With `$D01B` bit 0 set in
|
|
900
|
+
the black mode, 210 of the sprite's 504 pixels were black: the glyphs
|
|
901
|
+
under it still masked it. ECM with BMM (`$D011` written with bits 6 and
|
|
902
|
+
5) gave the same 63,496 black pixels and the same `$D01F` of `$01`.
|
|
903
|
+
|
|
904
|
+
Whether a real VIC of any revision matches VICE pixel for pixel in the
|
|
905
|
+
invalid modes is not measured here; the black output and the live
|
|
906
|
+
collisions are what the hardware page states and what the emulator
|
|
907
|
+
showed.
|
|
908
|
+
|
|
909
|
+
A mid-frame split that goes from a multicolour character zone to an ECM
|
|
910
|
+
zone meets the same pair for the cells between its two stores if it
|
|
911
|
+
writes `$D011` before `$D016`; `raster_split_modes` warns of an
|
|
912
|
+
"undefined intermediate mode" between the writes, and for this pair the
|
|
913
|
+
intermediate mode is black. How many cells that covers is not measured
|
|
914
|
+
here.
|
|
915
|
+
|
|
916
|
+
### Fix
|
|
917
|
+
|
|
918
|
+
Clear MCM before you set ECM, or in the same handful of cycles, and
|
|
919
|
+
never assume `$D016` from the last screen. Write the whole `$D016` byte
|
|
920
|
+
for the new screen (`$C8` for a 40-column, unscrolled ECM screen) rather
|
|
921
|
+
than leaving whatever the previous mode put there. If you must
|
|
922
|
+
read-modify-write, `and #$EF` clears the bit without disturbing CSEL and
|
|
923
|
+
XSCROLL. In a raster split from multicolour text to ECM, store `$D016`
|
|
924
|
+
first and `$D011` second, so the only intermediate state is plain text
|
|
925
|
+
rather than black.
|
|
926
|
+
|
|
927
|
+
Write a sanity check into the setup routine during development: after
|
|
928
|
+
the mode writes, `lda $d011`, `and #$40`, and if it is set, `lda $d016`,
|
|
929
|
+
`and #$10`, which must be zero. Bits 7 and 6 of `$D016` read as ones
|
|
930
|
+
(`vic_colour_register_upper_nibble_reads_set` above), so mask before
|
|
931
|
+
you compare.
|
|
932
|
+
|
|
933
|
+
### Worked example
|
|
934
|
+
|
|
935
|
+
The fragment builds as written. The first block is the bug as it
|
|
936
|
+
arrives from a multicolour screen; the second is the fix.
|
|
937
|
+
|
|
938
|
+
```asm
|
|
939
|
+
// Bad: $D016 still holds $D8 from the multicolour text screen.
|
|
940
|
+
// Setting ECM on top of it selects the ECM+MCM invalid mode.
|
|
941
|
+
lda #$d8 // MCM on, as the previous screen left it
|
|
942
|
+
sta $d016
|
|
943
|
+
lda $d011
|
|
944
|
+
ora #$40 // ECM on: the window goes black
|
|
945
|
+
sta $d011
|
|
946
|
+
|
|
947
|
+
// Good: put $D016 into its ECM-screen state first, then set ECM.
|
|
948
|
+
lda #$c8 // MCM off, CSEL on, XSCROLL 0
|
|
949
|
+
sta $d016
|
|
950
|
+
lda $d011
|
|
951
|
+
ora #$40 // ECM on: four background bands appear
|
|
952
|
+
sta $d011
|
|
953
|
+
|
|
954
|
+
// Also good, when $D016 carries scroll state you want to keep.
|
|
955
|
+
lda $d016
|
|
956
|
+
and #$ef // clear MCM only
|
|
957
|
+
sta $d016
|
|
958
|
+
lda $d011
|
|
959
|
+
ora #$40
|
|
960
|
+
sta $d011
|
|
961
|
+
rts
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
The runs behind the table used the register writes above on a screen of
|
|
965
|
+
four bands with one white sprite at (100, 110). Each row is the bytes a
|
|
966
|
+
`-moncommands` trace dumped after the program stored them, followed by
|
|
967
|
+
the exit screenshot's pixel count over the 320 by 200 window. The two
|
|
968
|
+
models gave identical bytes and identical counts:
|
|
969
|
+
|
|
970
|
+
```text
|
|
971
|
+
variant $D011 $D016 $D01F $D01F window pixels (PAL and NTSC)
|
|
972
|
+
+2 fr +3 fr
|
|
973
|
+
plain text (control) $1B $C8 $01 $01 64,000 non-black: blue field, white glyphs
|
|
974
|
+
A: ECM, $D016 = $C8 $DB $C8 $01 $01 64,000 non-black: blue/green/red/yellow bands
|
|
975
|
+
B: ECM, $D016 = $D8 $DB $D8 $01 $01 63,496 black + 504 white (the sprite)
|
|
976
|
+
B with $D01B bit 0 set $DB $D8 $01 $01 63,706 black + 294 white (sprite cut by glyphs)
|
|
977
|
+
C: ECM + BMM, $D016 = $C8 $FB $C8 $01 $01 63,496 black + 504 white (the sprite)
|
|
978
|
+
fix: $D016 -> $C8, then ECM $DB $C8 $01 $01 64,000 non-black, identical to A
|
|
979
|
+
|
|
980
|
+
Border pixel (2, 100): light blue in every run, both models.
|
|
981
|
+
$D01E: $00 in every run (one sprite).
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
`$D016` reads back with bits 7 and 6 set, so `$C8` is the byte written
|
|
985
|
+
as `$C8` and also the byte written as `$08`; the low nibble is what the
|
|
986
|
+
table is about.
|
|
987
|
+
|
|
988
|
+
### Cross-references
|
|
989
|
+
|
|
990
|
+
- Technique: `ecm_mode` (`techniques/bitmap-modes.md`), which names
|
|
991
|
+
ECM+BMM as invalid and is silent on ECM+MCM
|
|
992
|
+
- Technique: `mcm_text` (`techniques/bitmap-modes.md`), the screen that
|
|
993
|
+
leaves MCM set on the way in
|
|
994
|
+
- Technique: `raster_split_modes` (`techniques/raster.md`), whose
|
|
995
|
+
"undefined intermediate mode" between the `$D011` and `$D016` stores
|
|
996
|
+
is this pair when the split runs from multicolour text to ECM
|
|
997
|
+
- Registers: `$D011`, `$D016` (`hardware/vic-ii-reference.md`, the mode
|
|
998
|
+
table and "Illegal display modes")
|
|
999
|
+
- Pitfall: `d016_unmasked_rmw_clobbers_csel_mcm` (`pitfalls/scroll.md`),
|
|
1000
|
+
the other way to end up with the wrong MCM bit
|
|
1001
|
+
- Pitfall: `sprite_priority_collision_silent` (`pitfalls/sprite.md`),
|
|
1002
|
+
the `$D01F` read that clears; the reads in the table were the only
|
|
1003
|
+
reads after the mode write
|
|
1004
|
+
- Pitfall: `vic_colour_register_upper_nibble_reads_set` (this page), why
|
|
1005
|
+
the `$D016` check masks before comparing
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
## ctm_embedded_whole_shifts_charset — A CharPad `.ctm` embedded whole puts its header where glyph 0 should be and shifts every glyph
|
|
1010
|
+
|
|
1011
|
+
**Severity:** medium
|
|
1012
|
+
**Region:** both
|
|
1013
|
+
**Triggered by registers:** D018
|
|
1014
|
+
**Triggered by techniques:** tile_map_render
|
|
1015
|
+
|
|
1016
|
+
### Symptom
|
|
1017
|
+
|
|
1018
|
+
The program points `$D018` at a charset the build embedded from the
|
|
1019
|
+
artist's CharPad project file, and the screen is wrong everywhere at
|
|
1020
|
+
once. If the program cleared the screen to code 0 itself, every cleared
|
|
1021
|
+
cell shows the same small pattern, so the whole window is covered in
|
|
1022
|
+
it. If it cleared through the KERNAL (`CHR$(147)`, or `$E544`), the
|
|
1023
|
+
screen is full of `$20` instead, and that cell draws a mixture of two
|
|
1024
|
+
other shifted glyphs, so the window reads as garbage rather than as one
|
|
1025
|
+
repeated pattern (arithmetic from the shift below, not measured here).
|
|
1026
|
+
The glyphs the program did print are there in outline but not as
|
|
1027
|
+
drawn, each one a mixture of the tail of one source glyph and the head
|
|
1028
|
+
of the next. Text
|
|
1029
|
+
written with the same screen codes against a raw charset export comes
|
|
1030
|
+
out right, so the codes and the `$D018` value are not the problem. The
|
|
1031
|
+
assembler and the compiler both said nothing.
|
|
1032
|
+
|
|
1033
|
+
### Mechanism
|
|
1034
|
+
|
|
1035
|
+
A `.ctm` is CharPad's save format, not a charset. The file opens with
|
|
1036
|
+
the signature `CTM`, a version byte and a short fixed header, and then
|
|
1037
|
+
the data sections; the character section is first, but it is not at
|
|
1038
|
+
offset 0. On a version 8 file the header is 14 bytes and the character
|
|
1039
|
+
section opens with a 2-byte marker and a 2-byte count, so the first
|
|
1040
|
+
glyph row is at offset 18 (`$12`). The other versions put it elsewhere:
|
|
1041
|
+
20 (`$14`) on version 5, which has no markers, and 23 (`$17`) on
|
|
1042
|
+
version 9, whose header carries five extra grid bytes (both arithmetic
|
|
1043
|
+
from the field tables in `formats/c64-file-formats.md`, not measured
|
|
1044
|
+
here). After the glyphs come the materials, the optional tiles and the
|
|
1045
|
+
map, so the file is also longer than the charset it holds.
|
|
1046
|
+
|
|
1047
|
+
Oscar64's `#embed "file"` and KickAssembler's `.import binary "file"`
|
|
1048
|
+
both copy the file as written. The header lands on glyph 0, the marker
|
|
1049
|
+
and count follow it, and every glyph after that sits 18 bytes late: two
|
|
1050
|
+
whole glyphs and two rows. What the VIC draws for code `n` is the last
|
|
1051
|
+
two rows of source glyph `n - 3` followed by the first six rows of
|
|
1052
|
+
source glyph `n - 2`. Code 0, the code the rig below cleared the screen
|
|
1053
|
+
to, draws the eight bytes `43 54 4D 08 00 00 00 0E`, which is `CTM`,
|
|
1054
|
+
the version and the first half of the header, and that is the pattern
|
|
1055
|
+
that covers the window. A KERNAL clear fills the screen with code 32,
|
|
1056
|
+
which under the same shift is the last two rows of source glyph 29 and
|
|
1057
|
+
the first six of glyph 30 (arithmetic, not measured here).
|
|
1058
|
+
|
|
1059
|
+
Measured on the windowless x64sc build of VICE 3.10, PAL, with a
|
|
1060
|
+
synthetic version 8 file built in Python from the layout on the formats
|
|
1061
|
+
page (no CharPad file exists on this machine): 64 glyphs, no tiles, a
|
|
1062
|
+
40 by 25 map, 2,602 bytes, the same shape and size as the page's
|
|
1063
|
+
`introfont.ctm` sample. Glyph 0 was blank, glyph 1 solid, glyph 2 a
|
|
1064
|
+
checkerboard, glyph 3 a horizontal bar. The charset went to `$3000`
|
|
1065
|
+
and `$D018` to `$1C`. A `-moncommands` trace on the store to `$D018`
|
|
1066
|
+
dumped `$3000`:
|
|
1067
|
+
|
|
1068
|
+
- Oscar64 `#embed 2048 0 "font.ctm"`, and KickAssembler
|
|
1069
|
+
`.import binary "font.ctm"`, both gave `43 54 4D 08 00 00 00 0E 00
|
|
1070
|
+
0F 0C 09 08 07 DA B0 3F 00 00 00 00 00 00 00 00 00 FF FF FF FF FF
|
|
1071
|
+
FF`: header, marker, count, then glyph 0's zeros running into glyph
|
|
1072
|
+
1's `FF` rows two bytes late. Identical bytes from the two toolchains.
|
|
1073
|
+
- Oscar64 `#embed 512 18 "font.ctm"` and KickAssembler
|
|
1074
|
+
`.import binary "font.ctm", 18, 512` both gave `00 00 00 00 00 00 00
|
|
1075
|
+
00 FF FF FF FF FF FF FF FF AA 55 AA 55 AA 55 AA 55 00 00 00 FF FF 00
|
|
1076
|
+
00 00`, the four glyphs as drawn.
|
|
1077
|
+
|
|
1078
|
+
The exit screenshots agree. The whole-file runs lit 14,086 pixels of
|
|
1079
|
+
the 320 by 200 window with the screen cleared to code 0, and the code 0
|
|
1080
|
+
cell itself lit 14 pixels; the eight cells printed with codes 0 to 7
|
|
1081
|
+
lit 14, 20, 6, 48, 40, 24, 12 and 22. The skipped runs lit 332 pixels,
|
|
1082
|
+
the code 0 cell lit none, and the same eight cells lit 0, 64, 32, 16,
|
|
1083
|
+
16, 28, 8 and 8, which is blank, solid, checkerboard, bar, bar, box and
|
|
1084
|
+
two diagonals. Oscar64 and KickAssembler gave the same counts as each
|
|
1085
|
+
other in both cases.
|
|
1086
|
+
|
|
1087
|
+
The bare `#embed "font.ctm"` did not compile into a sized array: with
|
|
1088
|
+
`char Charset[2048]` Oscar64's first message was
|
|
1089
|
+
`font.ctm(2049, 1) : error 3006: '}' expected`, followed by a run of
|
|
1090
|
+
3037 and 3006 errors on the lines after it (the file's bytes past the
|
|
1091
|
+
2,048 the array holds), and with `char Charset[]` inside a 2 KB region
|
|
1092
|
+
it reported "Could not place object 'Charset'", size 2,602 against
|
|
1093
|
+
2,048. The two
|
|
1094
|
+
forms that build are the sized slice `#embed 2048 0`, which drops the
|
|
1095
|
+
tail, and an unsized array in a region wider than 2 KB, which put the
|
|
1096
|
+
same header at `$3000` and ran the data to `$3A2A`, 554 bytes into the
|
|
1097
|
+
next charset bank. KickAssembler's whole import did the same: the
|
|
1098
|
+
`.prg` for it was 12,843 bytes and the skipped one 10,753.
|
|
1099
|
+
|
|
1100
|
+
Within version 8 the character section is always first, so the 18-byte
|
|
1101
|
+
skip is a constant for that version on the file built here; it is not
|
|
1102
|
+
a constant across versions, and whether any CharPad option puts a
|
|
1103
|
+
variable-length section before the characters is not measured here.
|
|
1104
|
+
|
|
1105
|
+
### Fix
|
|
1106
|
+
|
|
1107
|
+
Skip the container. In Oscar64, `#embed 2048 18 "font.ctm"` takes the
|
|
1108
|
+
2 KB of glyphs from a 256-character version 8 file, or use the format-aware
|
|
1109
|
+
`#embed ctm_chars "font.ctm"`, which reads the header and the markers
|
|
1110
|
+
itself (versions 8 and 9 only; it takes a version 5 file without
|
|
1111
|
+
complaint and returns the wrong bytes, see the formats page). In
|
|
1112
|
+
KickAssembler, `.import binary "font.ctm", 18, $800`. Better than
|
|
1113
|
+
either: have the artist export the raw charset (CharPad's "Characters
|
|
1114
|
+
binary" export, per `art/asset-pipelines.md`, not measured here) and
|
|
1115
|
+
embed that, so the build does
|
|
1116
|
+
not carry the version-dependent offset at all; or strip the container
|
|
1117
|
+
in a build step with the walker on the formats page. Check the version
|
|
1118
|
+
byte before you settle on an offset.
|
|
1119
|
+
|
|
1120
|
+
A cheap guard for the build you have: read byte 0 of the charset bank
|
|
1121
|
+
before pointing `$D018` at it, and stop if it is `$43`, the `C` of
|
|
1122
|
+
`CTM`. A real glyph 0 can hold `$43` (it is a row of `01000011`), so
|
|
1123
|
+
the check is a tripwire during development, not a proof.
|
|
1124
|
+
|
|
1125
|
+
### Worked example
|
|
1126
|
+
|
|
1127
|
+
The four embed lines with what each put at `$3000` (measured above):
|
|
1128
|
+
|
|
1129
|
+
```text
|
|
1130
|
+
Oscar64: #embed 2048 0 "font.ctm" -> 43 54 4D 08 00 00 00 0E ... header on glyph 0
|
|
1131
|
+
Oscar64: #embed 512 18 "font.ctm" -> 00 00 00 00 00 00 00 00 FF FF ... glyphs
|
|
1132
|
+
|
|
1133
|
+
KickAss: .import binary "font.ctm" -> 43 54 4D 08 ... same 32 bytes as above
|
|
1134
|
+
KickAss: .import binary "font.ctm", 18, 512 -> 00 00 00 00 ... glyphs, same as above
|
|
1135
|
+
|
|
1136
|
+
The file built here holds 64 glyphs; a 256-glyph file takes 2048 in
|
|
1137
|
+
place of 512 (arithmetic, not measured here).
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
The tripwire, which builds as written:
|
|
1141
|
+
|
|
1142
|
+
```asm
|
|
1143
|
+
// Tripwire: refuse to switch charsets while the CTM header is on glyph 0.
|
|
1144
|
+
.label charset = $3000 // the bank the build embedded into
|
|
1145
|
+
|
|
1146
|
+
lda charset // byte 0 of glyph 0
|
|
1147
|
+
cmp #$43 // 'C' of "CTM": the container came along
|
|
1148
|
+
beq container
|
|
1149
|
+
lda #$1c // screen $0400, charset $3000
|
|
1150
|
+
sta $d018
|
|
1151
|
+
rts
|
|
1152
|
+
container:
|
|
1153
|
+
inc $d020 // flash the border and stop
|
|
1154
|
+
jmp container
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
### Cross-references
|
|
1158
|
+
|
|
1159
|
+
- Format: `.CTM` in `formats/c64-file-formats.md`, the per-version
|
|
1160
|
+
header tables, the section order and the walker that prints each
|
|
1161
|
+
section's offset
|
|
1162
|
+
- Toolchain: `#embed` in `toolchains/oscar64-reference.md`, the
|
|
1163
|
+
`LIMIT OFFSET` slice and the `ctm_chars` specifier;
|
|
1164
|
+
`.import binary` in `toolchains/kickassembler-reference.md`, the
|
|
1165
|
+
offset and length parameters
|
|
1166
|
+
- Technique: `tile_map_render` (`techniques/scroll.md`), whose CharPad
|
|
1167
|
+
paragraph names the `ctm_*` specifiers; `table_generation`
|
|
1168
|
+
(`techniques/cpu-cycle-tricks.md`), the slice form on a host-built
|
|
1169
|
+
table
|
|
1170
|
+
- Pipeline: `art/asset-pipelines.md`, "Charsets (.ctm from CharPad)",
|
|
1171
|
+
the raw export paths
|
|
1172
|
+
- Register: `$D018` (`hardware/vic-ii-reference.md`)
|