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,1000 @@
|
|
|
1
|
+
---
|
|
2
|
+
recipe: simple-shmup
|
|
3
|
+
toolchain: oscar64
|
|
4
|
+
output_format: PRG
|
|
5
|
+
region: both
|
|
6
|
+
techniques: [sprite_multiplex_8, soft_scroll_v, sid_play_routine_pattern]
|
|
7
|
+
file_formats: [PRG]
|
|
8
|
+
uses_registers: [D015, D000, D001, D010, D027, D028, D029, D02A, D02B, D02C, D02D, D02E, D01E, D01F, D011, D016, D012, D019, D01A, D400, D401, D402, D403, D404, D405, D406, D407, D408, D409, D40A, D40B, D40C, D40D, D40E, D40F, D410, D411, D412, D413, D414, D415, D416, D417, D418]
|
|
9
|
+
uses_kernal: []
|
|
10
|
+
scaffolds: [vertical_shmup, horizontal_shmup]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<!-- doc-type: recipe -->
|
|
14
|
+
|
|
15
|
+
# Oscar64 Simple Vertical-Scrolling Shoot-Em-Up
|
|
16
|
+
|
|
17
|
+
## Synopsis
|
|
18
|
+
|
|
19
|
+
A minimal but complete vertical-scrolling shoot-em-up implemented in Oscar64 C.
|
|
20
|
+
The player controls a ship via joystick port 2, fires bullets upward, and must
|
|
21
|
+
avoid enemies that enter from the top in sine-wave formations. A starfield scrolls
|
|
22
|
+
downward using `$D011` YSCROLL (one-pixel-per-frame soft scroll; an earlier
|
|
23
|
+
version of this sentence said `$D016`, which is XSCROLL). Eight hardware
|
|
24
|
+
sprites are multiplexed via `vspr_*` to display the player, four enemies, and up
|
|
25
|
+
to three active bullets simultaneously. Collisions are software bounding-box
|
|
26
|
+
tests; `$D01E`/`$D01F` are read only to clear them (see "Collision detection"
|
|
27
|
+
below — an earlier version of this sentence said collisions were detected
|
|
28
|
+
through `$D01E`). A background SID stub drives the play-routine pattern once per
|
|
29
|
+
frame from a raster IRQ. The program is fully playable: load in VICE, push joystick
|
|
30
|
+
port 2, and shoot the enemies.
|
|
31
|
+
|
|
32
|
+
This is the hero recipe of Phase 4. It demonstrates the full agent-loop
|
|
33
|
+
capability: an agent reading this recipe and the cross-referenced technique docs
|
|
34
|
+
can reconstruct a working shmup shell from scratch. Cross-references:
|
|
35
|
+
`docs/recipes/oscar64/sprite-multiplex-8.md` for `vspr_*` idioms,
|
|
36
|
+
`docs/recipes/oscar64/sid-music-player.md` for the play-routine pattern,
|
|
37
|
+
`docs/recipes/oscar64/soft-scroll-h.md` for `$D016` XSCROLL mechanics.
|
|
38
|
+
|
|
39
|
+
The `techniques` list names `soft_scroll_v`, the scroll this listing does
|
|
40
|
+
with `$D011` YSCROLL. An earlier version listed `soft_scroll_h` and
|
|
41
|
+
`sprite_collision_detect`; the listing never scrolls with `$D016`, and it
|
|
42
|
+
reads `$D01E` only to clear it, so neither technique is implemented here.
|
|
43
|
+
|
|
44
|
+
## Source
|
|
45
|
+
|
|
46
|
+
```c
|
|
47
|
+
// simple-shmup.c
|
|
48
|
+
//
|
|
49
|
+
// Minimal vertical-scrolling shoot-em-up for stock C64 PAL/NTSC.
|
|
50
|
+
//
|
|
51
|
+
// Logical sprite assignments (vspr indices):
|
|
52
|
+
// 0 player ship
|
|
53
|
+
// 1 bullet 0
|
|
54
|
+
// 2 bullet 1
|
|
55
|
+
// 3 bullet 2
|
|
56
|
+
// 4-7 enemies 0-3
|
|
57
|
+
//
|
|
58
|
+
// Hardware sprites: 8 slots multiplexed by vspr_* over two vertical passes.
|
|
59
|
+
//
|
|
60
|
+
// Screen layout:
|
|
61
|
+
// Row 0: HUD (score)
|
|
62
|
+
// Rows 1-24: play field with downward-scrolling starfield
|
|
63
|
+
//
|
|
64
|
+
// Compile:
|
|
65
|
+
// oscar64 -O2 -o=simple-shmup.prg -tf=prg simple-shmup.c
|
|
66
|
+
//
|
|
67
|
+
#include <c64/vic.h>
|
|
68
|
+
#include <c64/sid.h>
|
|
69
|
+
#include <c64/sprites.h>
|
|
70
|
+
#include <c64/rasterirq.h>
|
|
71
|
+
#include <c64/joystick.h>
|
|
72
|
+
#include <c64/memmap.h>
|
|
73
|
+
#include <string.h>
|
|
74
|
+
#include <stdlib.h>
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Memory layout
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Code + data below $2000
|
|
80
|
+
#pragma region( lower, 0x0a00, 0x2000, , , {code, data} )
|
|
81
|
+
|
|
82
|
+
// Sprite data at $2000 (32 x 64-byte blocks = 2048 bytes)
|
|
83
|
+
#pragma section( spriteset, 0 )
|
|
84
|
+
#pragma region( spriteset_region, 0x2000, 0x2800, , , {spriteset} )
|
|
85
|
+
|
|
86
|
+
// Everything else above $2800
|
|
87
|
+
#pragma region( main, 0x2800, 0xa000, , , {code, data, bss, heap, stack} )
|
|
88
|
+
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// Sprite bitmaps
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Block layout within $2000:
|
|
93
|
+
// Block 0: player ship
|
|
94
|
+
// Block 1: bullet
|
|
95
|
+
// Block 2: enemy
|
|
96
|
+
// Block 3: explosion frame 0
|
|
97
|
+
// Block 4: explosion frame 1
|
|
98
|
+
//
|
|
99
|
+
// Each block is 64 bytes; bytes 0-62 are the 21-row x 3-byte bitmap; byte 63 = 0.
|
|
100
|
+
//
|
|
101
|
+
// These bitmaps are generated at startup rather than embedded as binary because
|
|
102
|
+
// the recipe should compile standalone. A real game would use:
|
|
103
|
+
// static const char spriteset_data[] = { #embed spd_sprites "sprites.spd" };
|
|
104
|
+
|
|
105
|
+
#pragma data(spriteset)
|
|
106
|
+
static char sprite_blocks[2048]; // 32 x 64; only first 5 used
|
|
107
|
+
#pragma data(data)
|
|
108
|
+
|
|
109
|
+
// Draw sprite shapes into sprite_blocks at startup.
|
|
110
|
+
static void sprites_build(void)
|
|
111
|
+
{
|
|
112
|
+
// Block 0: player ship — pointed nose, wide body
|
|
113
|
+
{
|
|
114
|
+
char *b = sprite_blocks;
|
|
115
|
+
static const char ship[21*3] = {
|
|
116
|
+
0,3,0, 0,7,128, 0,15,192, 0,31,224,
|
|
117
|
+
0,63,240,0,127,248,1,255,252,3,255,254,
|
|
118
|
+
7,255,255,3,255,254,1,231,252,0,119,248,
|
|
119
|
+
0,119,248,0,119,248,0,63,240,0,63,240,
|
|
120
|
+
0,28,0, 0,28,0, 0,28,0, 0,8,0, 0,0,0
|
|
121
|
+
};
|
|
122
|
+
for (char i = 0; i < 63; i++) b[i] = ship[i];
|
|
123
|
+
b[63] = 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Block 1: bullet — narrow vertical line
|
|
127
|
+
{
|
|
128
|
+
char *b = sprite_blocks + 64;
|
|
129
|
+
for (char r = 0; r < 21; r++) {
|
|
130
|
+
b[r*3+0] = 0;
|
|
131
|
+
b[r*3+1] = (r < 7) ? 0x18 : 0;
|
|
132
|
+
b[r*3+2] = 0;
|
|
133
|
+
}
|
|
134
|
+
b[63] = 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Block 2: enemy — diamond shape
|
|
138
|
+
{
|
|
139
|
+
char *b = sprite_blocks + 128;
|
|
140
|
+
static const char enemy[21*3] = {
|
|
141
|
+
0,0,0, 0,24,0, 0,60,0, 0,126,0,
|
|
142
|
+
0,255,0,1,255,128,3,255,192,7,255,224,
|
|
143
|
+
15,255,240,7,255,224,3,255,192,1,255,128,
|
|
144
|
+
0,255,0,0,126,0,0,60,0,0,24,0,
|
|
145
|
+
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
|
146
|
+
};
|
|
147
|
+
for (char i = 0; i < 63; i++) b[i] = enemy[i];
|
|
148
|
+
b[63] = 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Block 3: explosion frame 0 — scattered dots
|
|
152
|
+
{
|
|
153
|
+
char *b = sprite_blocks + 192;
|
|
154
|
+
for (char r = 0; r < 21; r++) {
|
|
155
|
+
b[r*3+0] = (r & 3) == 0 ? 0x55 : 0;
|
|
156
|
+
b[r*3+1] = (r & 3) == 1 ? 0xAA : 0;
|
|
157
|
+
b[r*3+2] = (r & 3) == 2 ? 0x55 : 0;
|
|
158
|
+
}
|
|
159
|
+
b[63] = 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Block 4: explosion frame 1 — expanding ring
|
|
163
|
+
{
|
|
164
|
+
char *b = sprite_blocks + 256;
|
|
165
|
+
for (char r = 0; r < 21; r++) {
|
|
166
|
+
char v = (r == 3 || r == 17) ? 0xFF : (r == 10) ? 0x81 : 0;
|
|
167
|
+
b[r*3+0] = 0;
|
|
168
|
+
b[r*3+1] = v;
|
|
169
|
+
b[r*3+2] = 0;
|
|
170
|
+
}
|
|
171
|
+
b[63] = 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// Screen and color RAM
|
|
177
|
+
// ============================================================================
|
|
178
|
+
#define Screen ((byte *)0x0400)
|
|
179
|
+
#define Color ((byte *)0xd800)
|
|
180
|
+
#define COLS 40
|
|
181
|
+
#define ROWS 25
|
|
182
|
+
|
|
183
|
+
// Sprite data blocks start at $2000; block number = address / 64
|
|
184
|
+
#define SPRITE_BASE_BLOCK ((unsigned)sprite_blocks / 64) // = $2000/64 = 128
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Starfield
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// Stars are single space-inverted characters (block character) placed on the
|
|
190
|
+
// char layer. Each star has an (x, row) position. Every 8 frames the YSCROLL
|
|
191
|
+
// wraps and the star rows shift down one character; every frame the YSCROLL
|
|
192
|
+
// pixel offset moves the whole field downward by 1 px.
|
|
193
|
+
//
|
|
194
|
+
// This is the soft_scroll_h pattern adapted for vertical scrolling:
|
|
195
|
+
// $D011 bits 2-0 = YSCROLL (0-7): vertical fine scroll.
|
|
196
|
+
// Decrement each frame for downward scroll; at 0 -> carry the char layer.
|
|
197
|
+
|
|
198
|
+
#define NUM_STARS 16
|
|
199
|
+
|
|
200
|
+
static char star_x[NUM_STARS];
|
|
201
|
+
static char star_row[NUM_STARS];
|
|
202
|
+
static char yscroll; // current YSCROLL value, 0-7
|
|
203
|
+
|
|
204
|
+
// Star character: solid block (screen code $A0 = filled block in PETSCII)
|
|
205
|
+
#define STAR_CHAR 0xA0
|
|
206
|
+
#define SPACE_CHAR 0x20
|
|
207
|
+
|
|
208
|
+
// Initialize star positions.
|
|
209
|
+
static void stars_init(void)
|
|
210
|
+
{
|
|
211
|
+
yscroll = 7;
|
|
212
|
+
for (char i = 0; i < NUM_STARS; i++) {
|
|
213
|
+
star_x[i] = (char)(rand() % 40);
|
|
214
|
+
star_row[i] = (char)(1 + rand() % 24); // rows 1-24 (row 0 = HUD)
|
|
215
|
+
Screen[star_row[i] * COLS + star_x[i]] = STAR_CHAR;
|
|
216
|
+
Color [star_row[i] * COLS + star_x[i]] = VCOL_WHITE;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Called once per frame. Soft-scrolls the star layer downward.
|
|
221
|
+
static void stars_update(void)
|
|
222
|
+
{
|
|
223
|
+
if (yscroll > 0) {
|
|
224
|
+
yscroll--;
|
|
225
|
+
} else {
|
|
226
|
+
// Pixel wrap: shift all star rows down one character.
|
|
227
|
+
yscroll = 7;
|
|
228
|
+
for (char i = 0; i < NUM_STARS; i++) {
|
|
229
|
+
// Erase from current position
|
|
230
|
+
Screen[star_row[i] * COLS + star_x[i]] = SPACE_CHAR;
|
|
231
|
+
// Advance row (wrap at bottom back to row 1, skipping HUD row 0)
|
|
232
|
+
star_row[i]++;
|
|
233
|
+
if (star_row[i] >= ROWS)
|
|
234
|
+
star_row[i] = 1;
|
|
235
|
+
// Draw at new position (only if not occupied by a sprite cell)
|
|
236
|
+
Screen[star_row[i] * COLS + star_x[i]] = STAR_CHAR;
|
|
237
|
+
Color [star_row[i] * COLS + star_x[i]] = VCOL_WHITE;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Write YSCROLL into $D011, preserving DEN, BMM, ECM and RSEL but NOT
|
|
241
|
+
// bit 7: on a read, bit 7 is the CURRENT raster line's MSB, and on a
|
|
242
|
+
// write it is bit 8 of the raster-compare value. This runs right after
|
|
243
|
+
// rirq_wait(), i.e. with the beam past line 255, so a read-modify-write
|
|
244
|
+
// that kept bit 7 (mask 0xF8, as an earlier version had) moved the
|
|
245
|
+
// compare to line 256+ and the raster IRQ never fired again: the game
|
|
246
|
+
// froze after exactly one frame. Mask with 0x78.
|
|
247
|
+
// YSCROLL is bits 2-0; default KERNAL value is 3 (center).
|
|
248
|
+
vic.ctrl1 = (vic.ctrl1 & 0x78) | yscroll;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// Game objects: player, bullets, enemies
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
// --- Player ---
|
|
256
|
+
static int player_x; // screen pixels, range 24-319 (sprite X hardware)
|
|
257
|
+
static char player_y; // screen pixels, range 50-250 (sprite Y hardware)
|
|
258
|
+
static bool player_alive;
|
|
259
|
+
static char player_explode_timer;
|
|
260
|
+
|
|
261
|
+
#define PLAYER_INITIAL_X 160
|
|
262
|
+
#define PLAYER_INITIAL_Y 220
|
|
263
|
+
#define PLAYER_SPEED 2
|
|
264
|
+
#define PLAYER_SPRITE 0 // vspr index
|
|
265
|
+
|
|
266
|
+
// --- Bullets ---
|
|
267
|
+
#define MAX_BULLETS 3
|
|
268
|
+
#define BULLET_SPEED 3
|
|
269
|
+
|
|
270
|
+
struct Bullet {
|
|
271
|
+
int x;
|
|
272
|
+
char y;
|
|
273
|
+
bool active;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
static struct Bullet bullets[MAX_BULLETS];
|
|
277
|
+
static char fire_cooldown;
|
|
278
|
+
|
|
279
|
+
// --- Enemies ---
|
|
280
|
+
#define MAX_ENEMIES 4
|
|
281
|
+
#define ENEMY_SPEED 1
|
|
282
|
+
|
|
283
|
+
struct Enemy {
|
|
284
|
+
int x;
|
|
285
|
+
char y;
|
|
286
|
+
bool active;
|
|
287
|
+
char explode_timer;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
static struct Enemy enemies[MAX_ENEMIES];
|
|
291
|
+
static char enemy_wave_timer;
|
|
292
|
+
static char enemy_sine_phase;
|
|
293
|
+
|
|
294
|
+
// Sine table: 64 entries, range -32..+32 (amplitude of enemy X oscillation)
|
|
295
|
+
static char sine_tab[64];
|
|
296
|
+
|
|
297
|
+
// --- Score (displayed on HUD row 0) ---
|
|
298
|
+
static char score_digits[6]; // BCD-ish: each is 0-9, leftmost = most significant
|
|
299
|
+
|
|
300
|
+
static void score_add(char val)
|
|
301
|
+
{
|
|
302
|
+
// Add val to the 6-digit BCD score (rightmost digit first)
|
|
303
|
+
char carry = val;
|
|
304
|
+
for (char d = 5; d != 255 && carry; d--) {
|
|
305
|
+
char n = score_digits[d] + carry;
|
|
306
|
+
score_digits[d] = n % 10;
|
|
307
|
+
carry = n / 10;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
static void hud_draw(void)
|
|
312
|
+
{
|
|
313
|
+
// Row 0: "SCORE: XXXXXX"
|
|
314
|
+
// Screen-code note: ':' is 58 (NOT 26 — that's the letter 'Z').
|
|
315
|
+
// Letters A-Z map to 1-26 but punctuation lives in the 32-63 range
|
|
316
|
+
// matching ASCII positions.
|
|
317
|
+
static const char hud_label[] = {
|
|
318
|
+
// S C O R E : spc
|
|
319
|
+
19, 3, 15, 18, 5, 58, 32
|
|
320
|
+
};
|
|
321
|
+
for (char i = 0; i < 7; i++) {
|
|
322
|
+
Screen[i] = hud_label[i];
|
|
323
|
+
Color[i] = VCOL_YELLOW;
|
|
324
|
+
}
|
|
325
|
+
for (char d = 0; d < 6; d++) {
|
|
326
|
+
Screen[7 + d] = score_digits[d] + 48; // screen code for '0'..'9'
|
|
327
|
+
Color [7 + d] = VCOL_WHITE;
|
|
328
|
+
}
|
|
329
|
+
// Fill rest of HUD row
|
|
330
|
+
for (char i = 13; i < 40; i++) {
|
|
331
|
+
Screen[i] = SPACE_CHAR;
|
|
332
|
+
Color[i] = VCOL_BLACK;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ============================================================================
|
|
337
|
+
// Game init
|
|
338
|
+
// ============================================================================
|
|
339
|
+
static void game_init(void)
|
|
340
|
+
{
|
|
341
|
+
// Clear play field
|
|
342
|
+
for (int i = 40; i < 1000; i++) {
|
|
343
|
+
Screen[i] = SPACE_CHAR;
|
|
344
|
+
Color[i] = VCOL_BLACK;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Player
|
|
348
|
+
player_x = PLAYER_INITIAL_X;
|
|
349
|
+
player_y = PLAYER_INITIAL_Y;
|
|
350
|
+
player_alive = true;
|
|
351
|
+
player_explode_timer = 0;
|
|
352
|
+
|
|
353
|
+
// Bullets
|
|
354
|
+
for (char b = 0; b < MAX_BULLETS; b++)
|
|
355
|
+
bullets[b].active = false;
|
|
356
|
+
fire_cooldown = 0;
|
|
357
|
+
|
|
358
|
+
// Enemies
|
|
359
|
+
for (char e = 0; e < MAX_ENEMIES; e++)
|
|
360
|
+
enemies[e].active = false;
|
|
361
|
+
enemy_wave_timer = 0;
|
|
362
|
+
enemy_sine_phase = 0;
|
|
363
|
+
|
|
364
|
+
// Score
|
|
365
|
+
for (char d = 0; d < 6; d++)
|
|
366
|
+
score_digits[d] = 0;
|
|
367
|
+
|
|
368
|
+
// Build sine table for enemy X oscillation
|
|
369
|
+
// Use a simple integer sine approximation: half-wave quadrant fold
|
|
370
|
+
for (char i = 0; i < 64; i++) {
|
|
371
|
+
// Maps i to an approximate sine in range -32..+32
|
|
372
|
+
// Quarter-wave: 0..15 -> 0..32 (linear ramp as an approximation)
|
|
373
|
+
char q = i & 15;
|
|
374
|
+
char half = (i & 32) ? 1 : 0;
|
|
375
|
+
char asc = (i & 16) ? (15 - q) : q;
|
|
376
|
+
char amp = (char)(asc * 2); // 0..30
|
|
377
|
+
sine_tab[i] = half ? -(sbyte)amp : (sbyte)amp;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// Update: player
|
|
383
|
+
// ============================================================================
|
|
384
|
+
static void update_player(void)
|
|
385
|
+
{
|
|
386
|
+
if (!player_alive) {
|
|
387
|
+
if (player_explode_timer) {
|
|
388
|
+
player_explode_timer--;
|
|
389
|
+
// Animate explosion: alternate between explosion frames
|
|
390
|
+
char blk = SPRITE_BASE_BLOCK + 3 + (player_explode_timer & 1);
|
|
391
|
+
vspr_image(PLAYER_SPRITE, blk);
|
|
392
|
+
if (!player_explode_timer) {
|
|
393
|
+
vspr_hide(PLAYER_SPRITE);
|
|
394
|
+
// Restart after a short delay (handled outside this function
|
|
395
|
+
// by checking player_alive and respawning after some frames)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Read joystick port 2. Oscar64's joy_poll(n) reads $DC00+n, and $DC00
|
|
402
|
+
// (CIA1 port A) is joystick PORT 2; $DC01 is port 1. So joy_poll(0) is
|
|
403
|
+
// port 2, the usual player-1 port. An earlier version of this listing
|
|
404
|
+
// called joy_poll(1) here and read port 1 while claiming port 2.
|
|
405
|
+
joy_poll(0);
|
|
406
|
+
|
|
407
|
+
// Move player horizontally: clamp to sprite display range (24..311 for X)
|
|
408
|
+
player_x += (int)(joyx[0]) * PLAYER_SPEED;
|
|
409
|
+
if (player_x < 24) player_x = 24;
|
|
410
|
+
if (player_x > 311) player_x = 311;
|
|
411
|
+
|
|
412
|
+
// Move player vertically: clamp to rows 1-24 (Y 58..234 roughly)
|
|
413
|
+
player_y += (char)(joyy[0]) * (char)PLAYER_SPEED;
|
|
414
|
+
if (player_y < 58) player_y = 58;
|
|
415
|
+
if (player_y > 234) player_y = 234;
|
|
416
|
+
|
|
417
|
+
vspr_move(PLAYER_SPRITE, player_x, (int)player_y);
|
|
418
|
+
|
|
419
|
+
// Fire bullet on button press, with cooldown
|
|
420
|
+
if (fire_cooldown)
|
|
421
|
+
fire_cooldown--;
|
|
422
|
+
else if (joyb[0]) {
|
|
423
|
+
for (char b = 0; b < MAX_BULLETS; b++) {
|
|
424
|
+
if (!bullets[b].active) {
|
|
425
|
+
bullets[b].x = player_x;
|
|
426
|
+
bullets[b].y = player_y - 22; // spawn above ship
|
|
427
|
+
bullets[b].active = true;
|
|
428
|
+
vspr_set(b + 1, player_x, (int)bullets[b].y,
|
|
429
|
+
SPRITE_BASE_BLOCK + 1, VCOL_LT_GREEN);
|
|
430
|
+
// Trigger a short fire sound on SID voice 2
|
|
431
|
+
sid.voices[1].freq = SID_FREQ_PAL(1200);
|
|
432
|
+
sid.voices[1].attdec = SID_ATK_2 | SID_DKY_6;
|
|
433
|
+
sid.voices[1].susrel = 0;
|
|
434
|
+
sid.voices[1].ctrl = SID_CTRL_NOISE | SID_CTRL_GATE;
|
|
435
|
+
fire_cooldown = 8;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ============================================================================
|
|
443
|
+
// Update: bullets
|
|
444
|
+
// ============================================================================
|
|
445
|
+
static void update_bullets(void)
|
|
446
|
+
{
|
|
447
|
+
for (char b = 0; b < MAX_BULLETS; b++) {
|
|
448
|
+
if (!bullets[b].active)
|
|
449
|
+
continue;
|
|
450
|
+
|
|
451
|
+
bullets[b].y -= BULLET_SPEED;
|
|
452
|
+
|
|
453
|
+
// Deactivate when above top of play area (above raster line ~58)
|
|
454
|
+
if (bullets[b].y < 50) {
|
|
455
|
+
bullets[b].active = false;
|
|
456
|
+
vspr_hide(b + 1);
|
|
457
|
+
} else {
|
|
458
|
+
vspr_move(b + 1, bullets[b].x, (int)bullets[b].y);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ============================================================================
|
|
464
|
+
// Update: enemies
|
|
465
|
+
// ============================================================================
|
|
466
|
+
static void update_enemies(void)
|
|
467
|
+
{
|
|
468
|
+
// Spawn a new wave every 120 frames if fewer than MAX_ENEMIES are active
|
|
469
|
+
if (enemy_wave_timer) {
|
|
470
|
+
enemy_wave_timer--;
|
|
471
|
+
} else {
|
|
472
|
+
// Count active enemies
|
|
473
|
+
char active = 0;
|
|
474
|
+
for (char e = 0; e < MAX_ENEMIES; e++)
|
|
475
|
+
if (enemies[e].active) active++;
|
|
476
|
+
|
|
477
|
+
if (active == 0) {
|
|
478
|
+
// Spawn all 4 enemies in a horizontal formation at the top
|
|
479
|
+
for (char e = 0; e < MAX_ENEMIES; e++) {
|
|
480
|
+
enemies[e].x = 60 + (int)e * 64;
|
|
481
|
+
enemies[e].y = 55; // just below top border
|
|
482
|
+
enemies[e].active = true;
|
|
483
|
+
enemies[e].explode_timer = 0;
|
|
484
|
+
vspr_set(4 + e, enemies[e].x, (int)enemies[e].y,
|
|
485
|
+
SPRITE_BASE_BLOCK + 2, VCOL_RED);
|
|
486
|
+
}
|
|
487
|
+
enemy_wave_timer = 120;
|
|
488
|
+
enemy_sine_phase = 0;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Advance sine phase
|
|
493
|
+
enemy_sine_phase = (enemy_sine_phase + 1) & 63;
|
|
494
|
+
|
|
495
|
+
for (char e = 0; e < MAX_ENEMIES; e++) {
|
|
496
|
+
if (!enemies[e].active)
|
|
497
|
+
continue;
|
|
498
|
+
|
|
499
|
+
if (enemies[e].explode_timer) {
|
|
500
|
+
enemies[e].explode_timer--;
|
|
501
|
+
char blk = SPRITE_BASE_BLOCK + 3 + (enemies[e].explode_timer & 1);
|
|
502
|
+
vspr_image(4 + e, blk);
|
|
503
|
+
vspr_color(4 + e, VCOL_YELLOW);
|
|
504
|
+
if (!enemies[e].explode_timer) {
|
|
505
|
+
enemies[e].active = false;
|
|
506
|
+
vspr_hide(4 + e);
|
|
507
|
+
}
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Descend and oscillate horizontally
|
|
512
|
+
enemies[e].y += ENEMY_SPEED;
|
|
513
|
+
|
|
514
|
+
// Sine offset per enemy: stagger phase by 16 per enemy
|
|
515
|
+
char phase = (enemy_sine_phase + (char)(e << 4)) & 63;
|
|
516
|
+
enemies[e].x = 60 + (int)e * 64 + (int)(sbyte)sine_tab[phase] * 2;
|
|
517
|
+
|
|
518
|
+
// Clamp to display area
|
|
519
|
+
if (enemies[e].x < 24) enemies[e].x = 24;
|
|
520
|
+
if (enemies[e].x > 311) enemies[e].x = 311;
|
|
521
|
+
|
|
522
|
+
// Remove enemy when it exits the bottom
|
|
523
|
+
if (enemies[e].y > 250) {
|
|
524
|
+
enemies[e].active = false;
|
|
525
|
+
vspr_hide(4 + e);
|
|
526
|
+
} else {
|
|
527
|
+
vspr_move(4 + e, enemies[e].x, (int)enemies[e].y);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ============================================================================
|
|
533
|
+
// Collision detection via $D01E and $D01F
|
|
534
|
+
// ============================================================================
|
|
535
|
+
// Sprite index layout:
|
|
536
|
+
// vspr 0 = player -> hardware sprite 0 (after vspr_sort by Y)
|
|
537
|
+
// vspr 1-3 = bullets 0-2
|
|
538
|
+
// vspr 4-7 = enemies 0-3
|
|
539
|
+
//
|
|
540
|
+
// After vspr_sort and vspr_update, the vspr system maps logical sprites to
|
|
541
|
+
// hardware slots by ascending Y order. $D01E bits report which HARDWARE
|
|
542
|
+
// sprite slots collided -- we cannot directly match vspr index to hardware
|
|
543
|
+
// slot after sorting.
|
|
544
|
+
//
|
|
545
|
+
// Simpler approach for a shell game: use bounding-box collision in software.
|
|
546
|
+
// $D01E is still read and cleared each frame to prevent stale bits.
|
|
547
|
+
// For bullet-enemy we do AABB (axis-aligned bounding box) checks.
|
|
548
|
+
// For player-enemy we read $D01F (sprite-background) as a proxy indicator
|
|
549
|
+
// and also do AABB.
|
|
550
|
+
//
|
|
551
|
+
// Bounding box half-sizes (in screen pixels):
|
|
552
|
+
#define BULLET_HW 2
|
|
553
|
+
#define BULLET_HH 6
|
|
554
|
+
#define ENEMY_HW 10
|
|
555
|
+
#define ENEMY_HH 10
|
|
556
|
+
#define PLAYER_HW 11
|
|
557
|
+
#define PLAYER_HH 10
|
|
558
|
+
|
|
559
|
+
static void check_collisions(void)
|
|
560
|
+
{
|
|
561
|
+
// Read and clear collision registers (read-to-clear).
|
|
562
|
+
// We don't use the raw bits here but must clear them each frame.
|
|
563
|
+
// (GCC's __attribute__((unused)) is not Oscar64 C; the casts to void
|
|
564
|
+
// keep the reads and say the values are deliberately dropped. The field
|
|
565
|
+
// names are Oscar64's: spr_sprcol is $D01E, spr_backcol is $D01F.)
|
|
566
|
+
byte sps = vic.spr_sprcol;
|
|
567
|
+
byte spb = vic.spr_backcol;
|
|
568
|
+
(void)sps;
|
|
569
|
+
(void)spb;
|
|
570
|
+
|
|
571
|
+
if (!player_alive)
|
|
572
|
+
return;
|
|
573
|
+
|
|
574
|
+
for (char e = 0; e < MAX_ENEMIES; e++) {
|
|
575
|
+
if (!enemies[e].active || enemies[e].explode_timer)
|
|
576
|
+
continue;
|
|
577
|
+
|
|
578
|
+
// --- Bullet vs enemy AABB ---
|
|
579
|
+
for (char b = 0; b < MAX_BULLETS; b++) {
|
|
580
|
+
if (!bullets[b].active)
|
|
581
|
+
continue;
|
|
582
|
+
|
|
583
|
+
int dx = bullets[b].x - enemies[e].x;
|
|
584
|
+
char dy;
|
|
585
|
+
if (bullets[b].y > enemies[e].y)
|
|
586
|
+
dy = bullets[b].y - enemies[e].y;
|
|
587
|
+
else
|
|
588
|
+
dy = enemies[e].y - bullets[b].y;
|
|
589
|
+
|
|
590
|
+
if (dx < 0) dx = -dx;
|
|
591
|
+
|
|
592
|
+
// Compare dx as an int: |dx| can reach 287, and a (char) cast
|
|
593
|
+
// here (as an earlier version had) would fold 256..277 down to a
|
|
594
|
+
// false hit across the screen.
|
|
595
|
+
if (dx <= (BULLET_HW + ENEMY_HW) && dy <= (BULLET_HH + ENEMY_HH)) {
|
|
596
|
+
// Hit!
|
|
597
|
+
bullets[b].active = false;
|
|
598
|
+
vspr_hide(b + 1);
|
|
599
|
+
|
|
600
|
+
enemies[e].explode_timer = 12;
|
|
601
|
+
|
|
602
|
+
// Score
|
|
603
|
+
score_add(10);
|
|
604
|
+
hud_draw();
|
|
605
|
+
|
|
606
|
+
// Explosion sound on SID voice 2
|
|
607
|
+
sid.voices[1].freq = SID_FREQ_PAL(220);
|
|
608
|
+
sid.voices[1].attdec = SID_ATK_2 | SID_DKY_6;
|
|
609
|
+
sid.voices[1].susrel = (char)(0xF0 | SID_DKY_300);
|
|
610
|
+
sid.voices[1].ctrl = SID_CTRL_NOISE | SID_CTRL_GATE;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// --- Player vs enemy AABB ---
|
|
615
|
+
if (!enemies[e].explode_timer) {
|
|
616
|
+
int dx = player_x - enemies[e].x;
|
|
617
|
+
char dy;
|
|
618
|
+
if (player_y > enemies[e].y)
|
|
619
|
+
dy = player_y - enemies[e].y;
|
|
620
|
+
else
|
|
621
|
+
dy = enemies[e].y - player_y;
|
|
622
|
+
|
|
623
|
+
if (dx < 0) dx = -dx;
|
|
624
|
+
|
|
625
|
+
if (dx <= (PLAYER_HW + ENEMY_HW) && dy <= (PLAYER_HH + ENEMY_HH)) {
|
|
626
|
+
// Player hit!
|
|
627
|
+
player_alive = false;
|
|
628
|
+
player_explode_timer = 24;
|
|
629
|
+
enemies[e].explode_timer = 12;
|
|
630
|
+
|
|
631
|
+
vspr_image(PLAYER_SPRITE, SPRITE_BASE_BLOCK + 3);
|
|
632
|
+
vspr_color(PLAYER_SPRITE, VCOL_ORANGE);
|
|
633
|
+
|
|
634
|
+
sid.voices[1].freq = SID_FREQ_PAL(110);
|
|
635
|
+
sid.voices[1].attdec = SID_ATK_8 | SID_DKY_114;
|
|
636
|
+
sid.voices[1].susrel = (char)(0xF0 | SID_DKY_1500);
|
|
637
|
+
sid.voices[1].ctrl = SID_CTRL_NOISE | SID_CTRL_GATE;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// ============================================================================
|
|
644
|
+
// SID background music stub (play-routine pattern)
|
|
645
|
+
// ============================================================================
|
|
646
|
+
// A real game would embed a SID tune binary and call its play entry here.
|
|
647
|
+
// This stub plays a simple arpeggiated chord on voice 0 to demonstrate the
|
|
648
|
+
// per-frame play-routine pattern (sid_play_routine_pattern technique).
|
|
649
|
+
//
|
|
650
|
+
// The arpeggio cycles through three notes at 8 Hz (every 6 PAL frames).
|
|
651
|
+
static char music_tick = 0;
|
|
652
|
+
static char music_note = 0;
|
|
653
|
+
static const word music_arp[3] = {
|
|
654
|
+
NOTE_C(3), // C3
|
|
655
|
+
NOTE_E(3), // E3
|
|
656
|
+
NOTE_G(3) // G3
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
// Called once per frame from the raster IRQ (via rirq_call) or from
|
|
660
|
+
// main loop after rirq_wait(). Drives voice 0 arpeggio.
|
|
661
|
+
static void music_play(void)
|
|
662
|
+
{
|
|
663
|
+
music_tick++;
|
|
664
|
+
if (music_tick >= 6) {
|
|
665
|
+
music_tick = 0;
|
|
666
|
+
music_note = (music_note + 1) % 3;
|
|
667
|
+
sid.voices[0].freq = music_arp[music_note];
|
|
668
|
+
sid.voices[0].attdec = SID_ATK_2 | SID_DKY_24;
|
|
669
|
+
sid.voices[0].susrel = (char)(0x80 | SID_DKY_300);
|
|
670
|
+
sid.voices[0].ctrl = SID_CTRL_TRI | SID_CTRL_GATE;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Raster IRQ code block for the music play call
|
|
675
|
+
RIRQCode music_rirq;
|
|
676
|
+
|
|
677
|
+
// ============================================================================
|
|
678
|
+
// Respawn
|
|
679
|
+
// ============================================================================
|
|
680
|
+
static char respawn_timer = 0;
|
|
681
|
+
|
|
682
|
+
static void check_respawn(void)
|
|
683
|
+
{
|
|
684
|
+
if (!player_alive && !player_explode_timer) {
|
|
685
|
+
if (respawn_timer < 90) {
|
|
686
|
+
respawn_timer++;
|
|
687
|
+
} else {
|
|
688
|
+
respawn_timer = 0;
|
|
689
|
+
player_x = PLAYER_INITIAL_X;
|
|
690
|
+
player_y = PLAYER_INITIAL_Y;
|
|
691
|
+
player_alive = true;
|
|
692
|
+
vspr_set(PLAYER_SPRITE, player_x, (int)player_y,
|
|
693
|
+
SPRITE_BASE_BLOCK + 0, VCOL_LT_BLUE);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// ============================================================================
|
|
699
|
+
// main
|
|
700
|
+
// ============================================================================
|
|
701
|
+
int main(void)
|
|
702
|
+
{
|
|
703
|
+
mmap_trampoline();
|
|
704
|
+
mmap_set(MMAP_NO_BASIC);
|
|
705
|
+
|
|
706
|
+
// --- Build sprite bitmaps ---
|
|
707
|
+
sprites_build();
|
|
708
|
+
|
|
709
|
+
// --- Screen setup ---
|
|
710
|
+
vic.color_border = VCOL_BLACK;
|
|
711
|
+
vic.color_back = VCOL_BLACK;
|
|
712
|
+
for (int i = 0; i < 1000; i++) {
|
|
713
|
+
Screen[i] = SPACE_CHAR;
|
|
714
|
+
Color[i] = VCOL_BLACK;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// --- Initialize game state ---
|
|
718
|
+
game_init();
|
|
719
|
+
stars_init();
|
|
720
|
+
hud_draw();
|
|
721
|
+
|
|
722
|
+
// --- Initialize raster IRQ tables and the vspr multiplexer ---
|
|
723
|
+
// rirq_init MUST come first: vspr_init does not call it (sprites.c),
|
|
724
|
+
// and without it rirq_wait() never returns because the IRQ never reaches
|
|
725
|
+
// the rasterirq dispatcher. An earlier version of this listing omitted
|
|
726
|
+
// it and froze on the first rirq_wait() -- the screenshot never moved.
|
|
727
|
+
// true = KERNAL IRQ vector at $0314; KERNAL is still mapped under
|
|
728
|
+
// MMAP_NO_BASIC.
|
|
729
|
+
rirq_init(true);
|
|
730
|
+
|
|
731
|
+
// vspr_init reserves rirq slots 0..(VSPRITES_MAX-8) -- with the default
|
|
732
|
+
// VSPRITES_MAX=16 that is slots 0-7 (one reuse IRQ per sprite of the
|
|
733
|
+
// second pass, rows 80+4*i) plus slot 8 (a sync IRQ at row 250).
|
|
734
|
+
// vspr_update() re-arms or clears slots 0-7 EVERY frame.
|
|
735
|
+
vspr_init(Screen);
|
|
736
|
+
|
|
737
|
+
// Assign all 8 logical sprites initial positions (off-screen or visible)
|
|
738
|
+
vspr_set(PLAYER_SPRITE, player_x, (int)player_y,
|
|
739
|
+
SPRITE_BASE_BLOCK + 0, VCOL_LT_BLUE);
|
|
740
|
+
for (char b = 0; b < MAX_BULLETS; b++)
|
|
741
|
+
vspr_hide(b + 1);
|
|
742
|
+
for (char e = 0; e < MAX_ENEMIES; e++)
|
|
743
|
+
vspr_hide(4 + e);
|
|
744
|
+
|
|
745
|
+
vspr_sort();
|
|
746
|
+
vspr_update();
|
|
747
|
+
|
|
748
|
+
// --- Raster IRQ for music (first slot after the vspr slots) ---
|
|
749
|
+
// vspr_init owns slots 0..8 with the default VSPRITES_MAX=16, so the
|
|
750
|
+
// music IRQ goes in slot 9 (NUM_IRQS is 16). An earlier version used
|
|
751
|
+
// slot 2, which is one of the multiplexer's reuse slots: vspr_update()
|
|
752
|
+
// calls rirq_clear() on every unused reuse slot each frame, so the music
|
|
753
|
+
// call was silently cleared after the first frame.
|
|
754
|
+
rirq_build(&music_rirq, 1);
|
|
755
|
+
rirq_call(&music_rirq, 0, (void *)music_play);
|
|
756
|
+
// Note rasterirq programs row-1 into $D012, so row 0 is compare 255,
|
|
757
|
+
// i.e. the call physically lands at line 255, after the vspr sync IRQ
|
|
758
|
+
// at 250, not at the top of the frame.
|
|
759
|
+
rirq_set(9, 0, &music_rirq);
|
|
760
|
+
|
|
761
|
+
rirq_sort();
|
|
762
|
+
rirq_start();
|
|
763
|
+
|
|
764
|
+
// --- SID initialization ---
|
|
765
|
+
for (char r = 0; r < 25; r++)
|
|
766
|
+
((volatile byte *)0xd400)[r] = 0;
|
|
767
|
+
sid.fmodevol = 15; // master volume 15
|
|
768
|
+
|
|
769
|
+
// Seed voice 0 for the music arpeggio
|
|
770
|
+
sid.voices[0].pwm = 0x0800;
|
|
771
|
+
|
|
772
|
+
// Voice 1 (SFX) initial state
|
|
773
|
+
sid.voices[1].attdec = SID_ATK_2 | SID_DKY_6;
|
|
774
|
+
sid.voices[1].susrel = 0;
|
|
775
|
+
|
|
776
|
+
// ===================================================================
|
|
777
|
+
// Main loop
|
|
778
|
+
// ===================================================================
|
|
779
|
+
for (;;)
|
|
780
|
+
{
|
|
781
|
+
// 1. Wait for all raster IRQs of this frame to finish.
|
|
782
|
+
// This is the safe point to update vspr arrays: the sprite
|
|
783
|
+
// reuse IRQ has fired and will not read vspr data again until
|
|
784
|
+
// vspr_update reprograms it for the next frame.
|
|
785
|
+
rirq_wait();
|
|
786
|
+
|
|
787
|
+
// 2. Update game logic
|
|
788
|
+
update_player();
|
|
789
|
+
update_bullets();
|
|
790
|
+
update_enemies();
|
|
791
|
+
check_collisions();
|
|
792
|
+
check_respawn();
|
|
793
|
+
|
|
794
|
+
// 3. Stars (soft vertical scroll via $D011 YSCROLL)
|
|
795
|
+
stars_update();
|
|
796
|
+
|
|
797
|
+
// 4. Sprite multiplexer: sort -> update -> re-sort rirq slots
|
|
798
|
+
vspr_sort();
|
|
799
|
+
vspr_update();
|
|
800
|
+
rirq_sort();
|
|
801
|
+
|
|
802
|
+
// (music_play runs automatically from the music_rirq slot)
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
return 0;
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
## Build
|
|
810
|
+
|
|
811
|
+
```bash
|
|
812
|
+
oscar64 -O2 -o=simple-shmup.prg -tf=prg simple-shmup.c
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
Outputs: `simple-shmup.prg`, `.map`, `.asm`, `.lbl`.
|
|
816
|
+
|
|
817
|
+
Load in VICE: `LOAD"SIMPLE-SHMUP",8,1` then `RUN`, or use `-autostart
|
|
818
|
+
simple-shmup.prg`. Joystick port 2. Push to move; fire button to shoot.
|
|
819
|
+
|
|
820
|
+
## Expected output
|
|
821
|
+
|
|
822
|
+
Black screen with a top HUD row reading `SCORE: 000000` in yellow and white. A
|
|
823
|
+
light-blue player ship sprite sits at the bottom-center (sprite X 160, Y 220).
|
|
824
|
+
Sixteen white stars, each a solid 8x8 block character (`$A0`, not a dot), scroll
|
|
825
|
+
smoothly downward. Press the fire button: up to three light-green bullet sprites
|
|
826
|
+
advance upward and vanish at the top. Four red enemy diamond sprites are
|
|
827
|
+
spawned on the very first frame (the wave timer starts at 0 and no enemy is
|
|
828
|
+
active; an earlier version said "after 120 frames") and descend one pixel per
|
|
829
|
+
loop iteration while oscillating horizontally in a triangle-wave approximation
|
|
830
|
+
of a sine. When a bullet overlaps an enemy, the bullet vanishes, the enemy
|
|
831
|
+
flickers yellow (`VCOL_YELLOW`, not orange as an earlier version said) for 12
|
|
832
|
+
frames, and the score increments by 10. If an enemy reaches the player sprite,
|
|
833
|
+
both explode (the player in orange), and the player respawns after 90 frames. A
|
|
834
|
+
simple C-major-triad arpeggio plays on SID voice 0 throughout (not listened to
|
|
835
|
+
here).
|
|
836
|
+
|
|
837
|
+
Measured in headless VICE (PAL, 8,000,000 cycles, no joystick input): the
|
|
838
|
+
verify screenshot shows the HUD, the player at the bottom centre and four red
|
|
839
|
+
diamonds in a row about two-thirds of the way down the play field (sprite Y
|
|
840
|
+
about 178), with the star
|
|
841
|
+
blocks displaced from their start rows. In an instrumented build the main loop
|
|
842
|
+
had run 123 times while the music call had run about 245 times over the same
|
|
843
|
+
interval; whether the loop runs every second frame or the two counters measure
|
|
844
|
+
different intervals was not settled here.
|
|
845
|
+
|
|
846
|
+
## Why this works
|
|
847
|
+
|
|
848
|
+
### Game loop structure
|
|
849
|
+
|
|
850
|
+
The main loop follows the Oscar64 pattern shown in the `sprites.h` header
|
|
851
|
+
comment and in `samples/sprites/multiplexer.c` and `sprmux32.c` (an earlier
|
|
852
|
+
version pointed at `samples/games/breakout.c` and `hscrollshmup.c`, which do
|
|
853
|
+
not use `vspr_*` or `rirq_wait` at all). Both samples call `rirq_init()`
|
|
854
|
+
before `vspr_init()`; `vspr_init()` does not call it for you:
|
|
855
|
+
|
|
856
|
+
```
|
|
857
|
+
rirq_wait(); // sync: all IRQs for last frame are done
|
|
858
|
+
update_*(); // advance all game state
|
|
859
|
+
vspr_sort(); // order logical sprites by Y
|
|
860
|
+
vspr_update(); // reprogram hardware sprites + reuse IRQ slots
|
|
861
|
+
rirq_sort(); // re-sort IRQ slot table after vspr_update moved slots
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
`rirq_wait()` blocks until the last raster IRQ of the previous frame has fired.
|
|
865
|
+
This guarantees that `vspr_update()` is never called while the multiplexer IRQ
|
|
866
|
+
is reading the sprite slot table — a race condition that would corrupt sprite
|
|
867
|
+
positions. Everything between `rirq_wait()` and the bottom of the loop executes
|
|
868
|
+
in the vertical blank or during the active display period (for long games), but
|
|
869
|
+
the critical `vspr_update()` must happen before the raster beam reaches the first
|
|
870
|
+
reuse IRQ trigger line.
|
|
871
|
+
|
|
872
|
+
### Sprite multiplexer: eight slots for nine objects
|
|
873
|
+
|
|
874
|
+
`vspr_init(Screen)` initializes the `vspr_*` layer and claims raster IRQ slots
|
|
875
|
+
0 to `VSPRITES_MAX-8` for the multiplexer's internal use: with the default
|
|
876
|
+
`VSPRITES_MAX = 16` that is slots 0-7, one reuse IRQ per second-pass sprite
|
|
877
|
+
(initially rows 80, 84, ... 108), plus slot 8 for a sync IRQ at row 250 (read
|
|
878
|
+
from `sprites.c`; an earlier version of this paragraph said "slots 0 and 1").
|
|
879
|
+
The multiplexer supports up to 16 logical sprites displayed via two passes of 8
|
|
880
|
+
hardware sprites each. This recipe uses 8 logical sprites: one player, three
|
|
881
|
+
bullets, four enemies. Eight logical sprites fit in a single hardware pass, so
|
|
882
|
+
`vspr_update()` finds no ninth sprite and calls `rirq_clear()` on every reuse
|
|
883
|
+
slot each frame; only the sync IRQ fires. Anything you put in slots 0-7 is
|
|
884
|
+
therefore cleared on the next `vspr_update()`, which is why the music slot is 9.
|
|
885
|
+
With a second wave of enemies adding up to eight more sprites, the reuse IRQs
|
|
886
|
+
would automatically activate to handle the second group.
|
|
887
|
+
|
|
888
|
+
The sprite pointer block at `Screen + $3F8` (address `$07F8`) holds one byte per
|
|
889
|
+
hardware slot telling the VIC which 64-byte block of sprite data that slot
|
|
890
|
+
displays. `vspr_update()` writes this block along with the Y positions, X low
|
|
891
|
+
bytes, combined X-MSB byte (`$D010`), and color bytes (`$D027-$D02E`). The
|
|
892
|
+
linker region pragma places `sprite_blocks` at `$2000`; dividing by 64 gives
|
|
893
|
+
block number 128. `vspr_set(sp, x, y, 128+n, color)` assigns logical sprite
|
|
894
|
+
`sp` to display block `128+n`.
|
|
895
|
+
|
|
896
|
+
### Starfield via `$D011` YSCROLL soft scroll
|
|
897
|
+
|
|
898
|
+
`$D011` bits 2-0 (YSCROLL) shift the entire character display downward by
|
|
899
|
+
0-7 pixels. Incrementing YSCROLL each frame scrolls the display downward at
|
|
900
|
+
1 pixel per frame. When YSCROLL wraps from 7 back to 0, the character grid
|
|
901
|
+
snaps back 8 pixels upward; to cancel this visible snap, the character layer is
|
|
902
|
+
shifted down by one row simultaneously. This is the same carry mechanic described
|
|
903
|
+
in `docs/recipes/oscar64/soft-scroll-h.md` applied to the Y axis.
|
|
904
|
+
|
|
905
|
+
The star positions come from the C library's `rand()`, which is fine for a
|
|
906
|
+
demonstration and deterministic from one run to the next. A game wants a
|
|
907
|
+
seeded generator that costs a few cycles a call; that is `lfsr_random` in
|
|
908
|
+
`docs/techniques/maths.md`, with the recipe `lfsr-random.md` beside this one.
|
|
909
|
+
|
|
910
|
+
The star positions are tracked in the `star_row[]` array. Each star is a solid
|
|
911
|
+
block character (screen code `$A0`) drawn directly into the screen RAM. When the
|
|
912
|
+
carry fires (`yscroll` wraps), each star's row is incremented modulo 24 (rows
|
|
913
|
+
1-24, preserving HUD row 0). The write to `vic.ctrl1 = (vic.ctrl1 & 0x78) |
|
|
914
|
+
yscroll` preserves the DEN bit (display enable), the BMM bit (0 = text mode),
|
|
915
|
+
ECM and RSEL while updating YSCROLL — and deliberately drops bit 7. On a read
|
|
916
|
+
bit 7 is the current raster line's bit 8, on a write it is bit 8 of the raster
|
|
917
|
+
compare (`docs/hardware/vic-ii-reference.md`, `docs/pitfalls/raster-and-badline.md`).
|
|
918
|
+
This write runs just after `rirq_wait()` returns, with the beam past line 255,
|
|
919
|
+
so an earlier version of this listing that masked with `0xF8` and "preserved
|
|
920
|
+
the raster MSB" wrote a 1 back into the compare, moved the raster IRQ to line
|
|
921
|
+
256+, and froze the game after exactly one frame (measured: the exit screenshot
|
|
922
|
+
was identical at 8 and 12 million cycles). The read-modify-write is a single
|
|
923
|
+
expression thanks to Oscar64's volatile struct field access.
|
|
924
|
+
|
|
925
|
+
The cost per frame is one write to `$D011` plus (on carry frames, once per 8
|
|
926
|
+
frames) `NUM_STARS` screen RAM updates. At 16 stars that is 32 byte writes
|
|
927
|
+
per carry — negligible compared to the sprite update cost.
|
|
928
|
+
|
|
929
|
+
### Collision detection
|
|
930
|
+
|
|
931
|
+
The recipe uses software AABB (axis-aligned bounding box) collision because it
|
|
932
|
+
is more reliable than reading `$D01E` after `vspr_sort` has remapped logical
|
|
933
|
+
sprites to hardware slots. The hardware `$D01E` register reports which hardware
|
|
934
|
+
slot indices collided; after `vspr_sort`, logical sprite 0 (player) may be
|
|
935
|
+
mapped to hardware slot 3 or 7 depending on Y ordering. Mapping hardware bits
|
|
936
|
+
back to logical sprites requires tracking the sort-order mapping, which
|
|
937
|
+
`vspr_*` does not expose. Instead, `$D01E` and `$D01F` are read and discarded
|
|
938
|
+
each frame purely to reset the latches (preventing stale collision bits from
|
|
939
|
+
persisting across frames). The AABB tests are then performed directly on the
|
|
940
|
+
`player_x/y` and `enemies[e].x/y` coordinates, which are always in the correct
|
|
941
|
+
coordinate space.
|
|
942
|
+
|
|
943
|
+
AABB half-widths and heights are defined as constants matching the visual
|
|
944
|
+
extents of each sprite: the player ship is roughly 22x20 pixels, so `PLAYER_HW
|
|
945
|
+
= 11` and `PLAYER_HH = 10`. Enemy diamonds are roughly 20x20 pixels. Bullet
|
|
946
|
+
lines are thin (`BULLET_HW = 2`, `BULLET_HH = 6`). These values give
|
|
947
|
+
collisions that match the visual overlap to within a few pixels.
|
|
948
|
+
|
|
949
|
+
For a production game: read `$D01E` via a VIC collision IRQ (bit 1 of `$D01A`)
|
|
950
|
+
for sub-frame latency, use the bitmask to quickly narrow which hardware sprite
|
|
951
|
+
pairs are involved, then do an AABB test only for those pairs. This avoids
|
|
952
|
+
testing all O(N*M) pairs every frame.
|
|
953
|
+
|
|
954
|
+
### Enemy wave and sine oscillation
|
|
955
|
+
|
|
956
|
+
Enemies enter in waves of four when no enemies are active. The sine oscillation
|
|
957
|
+
is approximated with a 64-entry table generated at startup using integer linear
|
|
958
|
+
ramp: each quarter of the table ramps from 0 to 30 and back, giving a triangle
|
|
959
|
+
approximation to a sine with amplitude ±30 pixels. Each enemy is phase-shifted
|
|
960
|
+
by 16 table entries (90 degrees of the 64-entry cycle) so adjacent enemies
|
|
961
|
+
oscillate in a visually separated pattern rather than all moving together.
|
|
962
|
+
|
|
963
|
+
A real game would use an actual `sin()` lookup precomputed at compile time or
|
|
964
|
+
fetched from a table in `docs/techniques/effects-vector-3d.md`. The triangle
|
|
965
|
+
approximation suffices for the recipe's purpose: demonstrating the state machine
|
|
966
|
+
and entry/removal logic clearly.
|
|
967
|
+
|
|
968
|
+
### Music via `rirq_call` and the play-routine pattern
|
|
969
|
+
|
|
970
|
+
The arpeggio function `music_play()` is installed as a raster IRQ call in slot
|
|
971
|
+
9 with row 0 using `rirq_call` (rasterirq programs row-1 into `$D012`, so the
|
|
972
|
+
call actually lands at line 255, after the vspr sync IRQ at 250). This is the `sid_play_routine_pattern` technique from
|
|
973
|
+
`docs/techniques/music-sid.md` applied to a hand-written play stub rather than
|
|
974
|
+
an embedded SID binary. Each call advances `music_tick`; every 6 frames it
|
|
975
|
+
writes a new note frequency to SID voice 0 and re-gates the envelope. The call
|
|
976
|
+
runs at the top of every frame in the raster IRQ context, identical to how a
|
|
977
|
+
GoatTracker-generated play routine would be invoked.
|
|
978
|
+
|
|
979
|
+
To replace the stub arpeggio with a real SID tune, add the memory layout and
|
|
980
|
+
function-pointer infrastructure from `docs/recipes/oscar64/sid-music-player.md`,
|
|
981
|
+
point `rirq_call` at the tune's play address, and call the tune's init address
|
|
982
|
+
after `rirq_start()`. The rest of the shmup is unchanged.
|
|
983
|
+
|
|
984
|
+
### `__striped` alternative for larger games
|
|
985
|
+
|
|
986
|
+
This recipe uses separate struct arrays (`struct Bullet bullets[3]`,
|
|
987
|
+
`struct Enemy enemies[4]`) whose fields are accessed by struct dereference.
|
|
988
|
+
Oscar64's optimizer generates efficient code for small structs (up to 8 bytes)
|
|
989
|
+
because it can hold the base pointer in zero-page and access fields directly.
|
|
990
|
+
For larger arrays (16+ enemies), the idiomatic Oscar64 pattern is `__striped`:
|
|
991
|
+
|
|
992
|
+
```c
|
|
993
|
+
__striped struct Enemy { int x; char y; bool active; char timer; } enemies[16];
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
With `__striped`, all `enemies[i].x` values are contiguous in memory, then all
|
|
997
|
+
`enemies[i].y`, etc. The compiler accesses `enemies[i].x` as `LDA x_base, Y`
|
|
998
|
+
without a multiply, cutting the per-element access cost from 4+ cycles to 2.
|
|
999
|
+
The current recipe's 4-enemy array is small enough that the difference is not
|
|
1000
|
+
measurable; `__striped` becomes important when enemy counts exceed 8.
|