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,1467 @@
|
|
|
1
|
+
---
|
|
2
|
+
recipe: platformer-scaffold
|
|
3
|
+
toolchain: oscar64
|
|
4
|
+
output_format: PRG
|
|
5
|
+
region: both
|
|
6
|
+
techniques: [tile_map_render, tile_grid_collision, fixed_point_8_8, jump_arc_table, joystick_edge_detect, joystick_autorepeat, object_pool, lfsr_random, frame_sync_loop, decimal_print, sid_play_routine_pattern, sfx_engine_beside_music, kernal_file_write_seq, kernal_file_read_seq, error_channel_check]
|
|
7
|
+
file_formats: [PRG]
|
|
8
|
+
uses_registers: [D000, D001, D010, D011, D012, D015, D019, D01A, D020, D021, D027, D028, D029, D02A, D02B, D02C, D02D, D400, D401, D402, D403, D404, D405, D406, D407, D408, D409, D40A, D40B, D40C, D40D, D40E, D40F, D410, D411, D412, D413, D414, D418, D41B, DC00, DC02, DC03, DC06, DC07, DC0F]
|
|
9
|
+
uses_kernal: [SETLFS, SETNAM, OPEN, CLOSE, CHKIN, CHKOUT, CLRCHN, CHRIN, CHROUT, READST]
|
|
10
|
+
scaffolds: [single_screen_platformer]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<!-- doc-type: recipe -->
|
|
14
|
+
|
|
15
|
+
# Oscar64 Platformer Scaffold
|
|
16
|
+
|
|
17
|
+
## Synopsis
|
|
18
|
+
|
|
19
|
+
A single-screen platformer in one C file, meant to be copied as the
|
|
20
|
+
starting point of a new game rather than read as a demonstration. It has
|
|
21
|
+
the parts every platformer needs and nothing else: a tile map decoded from
|
|
22
|
+
RLE with row checksums, a player with 8.8 fixed-point motion and a jump
|
|
23
|
+
arc from a table, tile-grid collision at the body's corners, ladders, a
|
|
24
|
+
joystick reader with press events and a stated auto-repeat rule, a pool of
|
|
25
|
+
six enemies fed by a wave table and a Galois LFSR, a raster-synced frame
|
|
26
|
+
loop with a CIA timer over the whole loop body and a budget bar in the
|
|
27
|
+
border, a decimal HUD, a jump effect beside a stub three-voice tune, and
|
|
28
|
+
a high score written to disk at game over and read back at the next
|
|
29
|
+
start. A result byte at `$02FF` and the border colour report the verdict
|
|
30
|
+
so a headless run needs no human.
|
|
31
|
+
|
|
32
|
+
The `techniques` list did not name `sid_play_routine_pattern` until #22
|
|
33
|
+
step 3, although `tune_play` plays a three-voice stub every frame (row 8
|
|
34
|
+
below); a budget built from the list left the tune out.
|
|
35
|
+
|
|
36
|
+
It is the game the graph arm of the KB build test wrote from this
|
|
37
|
+
knowledge base, made self-contained and given a fixed layout. Every part
|
|
38
|
+
below names the recipe it was lifted from; an agent that has read those
|
|
39
|
+
pages will recognise the listings. `AUTOPILOT` is on by default so the
|
|
40
|
+
pinned run plays itself; set it to 0 for a build that reads control port 2.
|
|
41
|
+
|
|
42
|
+
## Memory plan
|
|
43
|
+
|
|
44
|
+
Nothing is placed by pragma. Oscar64's default `c64` target lays the
|
|
45
|
+
program out from `$0801` and this page reads the addresses off the
|
|
46
|
+
`.map` file the compiler wrote, so they are the figures for this build
|
|
47
|
+
and move when the code does.
|
|
48
|
+
|
|
49
|
+
| What | Where | Why there |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| BASIC stub and start-up | `$0801` to `$0853` | Oscar64's default; `RUN` or `-autostart` enters here |
|
|
52
|
+
| Code | `$0880` to `$11FE` | default code section; 6,473 bytes of PRG in all, including the two-byte load address |
|
|
53
|
+
| Constant data (RLE map stream, checksums, jump table, wave table, sprite rows) | `$11FE` to `$2148` | default data section, straight after the code |
|
|
54
|
+
| Variables (`bss`) | `$2148` to `$25D5` | the decoded map is first at `$2148` (880 bytes, 40 by 22); the high-score record, its read-back buffer and the 40-byte drive reply follow |
|
|
55
|
+
| Software stack | grows down from `$9FFE` | Oscar64's default under BASIC ROM |
|
|
56
|
+
| Sprite shapes | `$0340` to `$03FF` | the cassette buffer, blocks 13, 14 and 15 of VIC bank 0, so no bank switch and no region pragma; three shapes is all a scaffold needs and the buffer holds exactly three |
|
|
57
|
+
| Screen | `$0400`, colour at `$D800` | the KERNAL's own screen; rows 0 and 1 are the HUD, rows 2 to 23 the map, row 24 the drive reply |
|
|
58
|
+
| Character set | ROM upper-case set | no custom charset, so nothing to place; the tiles are `$A0` (solid block) and `H` |
|
|
59
|
+
|
|
60
|
+
When a game outgrows this, the first thing to move is the sprite shapes:
|
|
61
|
+
the KERNAL's tape routines own the cassette buffer, and a game with more
|
|
62
|
+
than three shapes wants a `#pragma region` block of its own, which
|
|
63
|
+
`docs/recipes/oscar64/memory-layout.md` shows. The map array is the
|
|
64
|
+
second: a scroller wants it under the VIC's bank or in a second buffer.
|
|
65
|
+
|
|
66
|
+
## Module skeleton
|
|
67
|
+
|
|
68
|
+
Build in this order. Each step is one function or one group, each names
|
|
69
|
+
the brief requirement it owns, and each was runnable on its own in the
|
|
70
|
+
original build before the next was added.
|
|
71
|
+
|
|
72
|
+
| Order | Function or group | Owns | Lifted from |
|
|
73
|
+
|---|---|---|---|
|
|
74
|
+
| 1 | `fault`, `ASSERT`, `RESULT` | the verdict: `$02FF` = 1 pass, 2 fail; border green or red | `headless-verify.md` |
|
|
75
|
+
| 2 | `put_str`, `put_dec`, `put_petscii` | screen-code text, decimal without division, PETSCII from the drive | `print-number.md` (the subtract-powers-of-ten loop; the original build divided and the HUD ate the frame) |
|
|
76
|
+
| 3 | `rnd` | 16-bit Galois LFSR, taps `$B400`, seeded from SID voice 3 | `lfsr-random.md` |
|
|
77
|
+
| 4 | `rle_row`, `fold`, `map_decode_and_draw`, `tile_at`, `solid_at` | the map: RLE rows, row checksums, the decoded grid, the tile lookups | `tile-map-render.md` for the format and decoder; `tile-grid-collision.md` for the lookups |
|
|
78
|
+
| 5 | `player_reset`, `player_update`, `player_draw` | 8.8 position, the jump table, gravity after the table, corner probes, landing snap, head bump, ladders | `fixed-point-jump.md` and `tile-grid-collision.md` |
|
|
79
|
+
| 6 | `joy_edge`, `repeat_step`, `autopilot_port` | press events by previous-frame comparison, auto-repeat after 20 frames then every 4, a synthetic port byte when `AUTOPILOT` is on | `joystick-input.md`; the autopilot is this page's own |
|
|
80
|
+
| 7 | `pool_alloc`, `spawn_enemy`, `wave_step`, `update_enemies`, `hit_enemy` | six slots in parallel arrays, a wave table on a 450-frame cycle, despawn off screen, a software box test | `object-pool.md` |
|
|
81
|
+
| 8 | `tune_play`, `sfx_jump`, `sfx_update` | a stub tune on three voices every frame, the effect borrowing voice 2 after it | `sfx-engine.md` (nobody has listened to either) |
|
|
82
|
+
| 9 | `drive_reply`, `hs_load`, `hs_save`, `hs_start`, `hs_game_over` | a five-byte SEQ record, first run gets 62 and writes, later runs load, replace is scratch then write, the no-device guard, the error channel on the bottom row | `high-score-persist.md` |
|
|
83
|
+
| 10 | `on_frame`, `timer_start`, `timer_stop`, `hud_draw`, `main` | a raster IRQ at row 250 bumping a tick byte, the loop waiting on it, CIA1 timer B across the loop body, dropped-frame count, the budget bar under `BUDGET_BAR` | `frame-sync-loop.md`, with the timer harness from `tile-map-render.md` |
|
|
84
|
+
|
|
85
|
+
The tables in the listing were generated. The generator is at the end
|
|
86
|
+
of this page in a text fence; run it when the map or the jump changes and
|
|
87
|
+
paste its output over the block marked "Generated tables".
|
|
88
|
+
|
|
89
|
+
## Source
|
|
90
|
+
|
|
91
|
+
```c
|
|
92
|
+
// platformer-scaffold.c -- a single-screen platformer for the stock C64,
|
|
93
|
+
// Oscar64, in one file. Copy it, keep the skeleton, replace the art and
|
|
94
|
+
// the map. Every part names the KB recipe it was lifted from:
|
|
95
|
+
//
|
|
96
|
+
// tile map recipes/oscar64/tile-map-render.md (RLE rows, no offset table)
|
|
97
|
+
// collision recipes/oscar64/tile-grid-collision.md (probe the body corners)
|
|
98
|
+
// jump / 8.8 recipes/oscar64/fixed-point-jump.md (gravity first, then position)
|
|
99
|
+
// joystick recipes/oscar64/joystick-input.md (joy_edge, repeat_step)
|
|
100
|
+
// object pool recipes/oscar64/object-pool.md (parallel arrays, scan alloc)
|
|
101
|
+
// LFSR recipes/oscar64/lfsr-random.md (16-bit Galois, taps $B400)
|
|
102
|
+
// frame loop recipes/oscar64/frame-sync-loop.md (rirq at row 250, tick byte, budget bar)
|
|
103
|
+
// decimal HUD recipes/oscar64/print-number.md (subtract powers of ten)
|
|
104
|
+
// sfx + tune stub recipes/oscar64/sfx-engine.md (voice 2 borrowed from the tune)
|
|
105
|
+
// high score recipes/oscar64/high-score-persist.md (kernalio.h, scratch then write)
|
|
106
|
+
// result byte recipes/oscar64/headless-verify.md ($02FF, border 5/2)
|
|
107
|
+
//
|
|
108
|
+
// Build: oscar64 -tm=c64 -O2 -o=platformer-scaffold.prg platformer-scaffold.c
|
|
109
|
+
// Defines: AUTOPILOT=1 (default) drives the joystick from the LFSR so a
|
|
110
|
+
// headless run plays itself; 0 reads control port 2.
|
|
111
|
+
// BUDGET_BAR=1 (default) paints the border while the loop works.
|
|
112
|
+
// DISK_WAIT_FRAMES=50 (default) frames to wait before the first OPEN.
|
|
113
|
+
|
|
114
|
+
#include <c64/vic.h>
|
|
115
|
+
#include <c64/cia.h>
|
|
116
|
+
#include <c64/sid.h>
|
|
117
|
+
#include <c64/sprites.h>
|
|
118
|
+
#include <c64/rasterirq.h>
|
|
119
|
+
#include <c64/kernalio.h>
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Generated tables. These were produced by the gen.py script quoted on the
|
|
123
|
+
// recipe page; regenerate them there when you change the map or the jump.
|
|
124
|
+
#define MAP_W 40
|
|
125
|
+
#define MAP_H 22
|
|
126
|
+
#define MAP_STREAM_LEN 244
|
|
127
|
+
static const char map_rle[] = {
|
|
128
|
+
0x01, 0x01, 0xa6, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0xa6, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01,
|
|
129
|
+
0xa6, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0xa6, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x82, 0x00,
|
|
130
|
+
0x89, 0x02, 0x8c, 0x00, 0x8b, 0x02, 0x84, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x8a, 0x00, 0x01,
|
|
131
|
+
0x03, 0x9b, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x8a, 0x00, 0x01, 0x03, 0x9b, 0x00, 0x01, 0x01,
|
|
132
|
+
0x00, 0x01, 0x01, 0x8a, 0x00, 0x01, 0x03, 0x9b, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x84, 0x00,
|
|
133
|
+
0x8a, 0x02, 0x01, 0x03, 0x89, 0x02, 0x85, 0x00, 0x87, 0x02, 0x82, 0x00, 0x01, 0x01, 0x00, 0x01,
|
|
134
|
+
0x01, 0x8e, 0x00, 0x01, 0x03, 0x97, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x8e, 0x00, 0x01, 0x03,
|
|
135
|
+
0x97, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x8e, 0x00, 0x01, 0x03, 0x97, 0x00, 0x01, 0x01, 0x00,
|
|
136
|
+
0x01, 0x01, 0x89, 0x02, 0x85, 0x00, 0x01, 0x03, 0x84, 0x00, 0x8b, 0x02, 0x88, 0x00, 0x01, 0x01,
|
|
137
|
+
0x00, 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8e, 0x00, 0x82, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00,
|
|
138
|
+
0x01, 0x03, 0x8f, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8f, 0x00, 0x01,
|
|
139
|
+
0x01, 0x00, 0x01, 0x01, 0x83, 0x00, 0x8e, 0x02, 0x85, 0x00, 0x01, 0x03, 0x84, 0x00, 0x89, 0x02,
|
|
140
|
+
0x82, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8f, 0x00, 0x01, 0x01, 0x00,
|
|
141
|
+
0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8f, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00, 0x01,
|
|
142
|
+
0x03, 0x8f, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x96, 0x00, 0x01, 0x03, 0x8f, 0x00, 0x01, 0x01,
|
|
143
|
+
0x00, 0xa8, 0x01, 0x00,
|
|
144
|
+
};
|
|
145
|
+
static const unsigned map_row_chk[MAP_H] = {
|
|
146
|
+
0x72be, 0x72be, 0x72be, 0x72be, 0x739e, 0xadb3, 0xadb3, 0xadb3, 0xf563, 0x5f23, 0x5f23, 0x5f23, 0x57ff, 0xff26, 0xff03, 0xff03, 0x3d61, 0xff03, 0xff03, 0xff03, 0xff03, 0xf010
|
|
147
|
+
};
|
|
148
|
+
#define JUMP_FRAMES 45
|
|
149
|
+
static const int jump_vy[JUMP_FRAMES] = {
|
|
150
|
+
-856, -816, -776, -736, -696, -656, -616, -576,
|
|
151
|
+
-536, -496, -456, -416, -376, -336, -296, -256,
|
|
152
|
+
-216, -176, -136, -96, -56, -16, 24, 64,
|
|
153
|
+
104, 144, 184, 224, 264, 304, 344, 384,
|
|
154
|
+
424, 464, 504, 544, 584, 624, 664, 704,
|
|
155
|
+
744, 784, 824, 864, 904,
|
|
156
|
+
};
|
|
157
|
+
#define N_PLATFORM_ROWS 5
|
|
158
|
+
static const char platform_rows[N_PLATFORM_ROWS] = { 4, 8, 12, 16, 21 };
|
|
159
|
+
|
|
160
|
+
#ifndef AUTOPILOT
|
|
161
|
+
#define AUTOPILOT 1
|
|
162
|
+
#endif
|
|
163
|
+
#ifndef BUDGET_BAR
|
|
164
|
+
#define BUDGET_BAR 1
|
|
165
|
+
#endif
|
|
166
|
+
|
|
167
|
+
#define SCREEN ((char *)0x0400)
|
|
168
|
+
#define COLOUR ((char *)0xd800)
|
|
169
|
+
#define SPR_PLAYER ((char *)0x0340) // block 13, cassette buffer
|
|
170
|
+
#define SPR_ENEMY_A ((char *)0x0380) // block 14
|
|
171
|
+
#define SPR_ENEMY_B ((char *)0x03c0) // block 15
|
|
172
|
+
#define RESULT (*(volatile char *)0x02ff)
|
|
173
|
+
|
|
174
|
+
#define CODE_PASS 0x01
|
|
175
|
+
#define CODE_FAIL 0x02
|
|
176
|
+
|
|
177
|
+
#define MAP_ROW 2 // first screen row of the map
|
|
178
|
+
#define HUD_ROW 0
|
|
179
|
+
#define CYC_ROW 1
|
|
180
|
+
#define DRIVE_ROW 24
|
|
181
|
+
|
|
182
|
+
#define SYNC_ROW 250 // rirq row; IRQ lands on line 251
|
|
183
|
+
|
|
184
|
+
#define PASS_FRAME 600 // autopilot: frames without fault
|
|
185
|
+
#define FORCE_OVER 800 // autopilot: force game over here
|
|
186
|
+
|
|
187
|
+
// Tiles
|
|
188
|
+
#define T_EMPTY 0
|
|
189
|
+
#define T_BRICK 1
|
|
190
|
+
#define T_PLAT 2
|
|
191
|
+
#define T_LADDER 3
|
|
192
|
+
|
|
193
|
+
// Joystick bits (active low on the port)
|
|
194
|
+
#define JOY_UP 0x01
|
|
195
|
+
#define JOY_DOWN 0x02
|
|
196
|
+
#define JOY_LEFT 0x04
|
|
197
|
+
#define JOY_RIGHT 0x08
|
|
198
|
+
#define JOY_FIRE 0x10
|
|
199
|
+
#define JOY_MASK 0x1f
|
|
200
|
+
#define REPEAT_DELAY 20
|
|
201
|
+
#define REPEAT_RATE 4
|
|
202
|
+
|
|
203
|
+
// Physics (8.8)
|
|
204
|
+
#define WALK_SPEED 0x0140 // 1.25 px/frame
|
|
205
|
+
#define GRAVITY 0x0028 // the G of the table generator
|
|
206
|
+
#define MAX_FALL 0x0400 // 4 px/frame
|
|
207
|
+
#define CLIMB_SPEED 1 // px/frame on a ladder
|
|
208
|
+
|
|
209
|
+
// Player body inside the 24x21 sprite: x 8..15, y 5..20
|
|
210
|
+
#define BODY_L 8
|
|
211
|
+
#define BODY_R 15
|
|
212
|
+
#define BODY_T 5
|
|
213
|
+
#define BODY_B 20
|
|
214
|
+
#define PLAYER_START_X 32 // world px (sprite x - 24)
|
|
215
|
+
#define PLAYER_START_Y (21*8 - 21) // feet on the floor row (tile row 21)
|
|
216
|
+
|
|
217
|
+
// Object pool
|
|
218
|
+
#define MAX_OBJ 6
|
|
219
|
+
#define OBJ_FREE 0
|
|
220
|
+
#define OBJ_ALIVE 1
|
|
221
|
+
#define NO_SLOT 0xff
|
|
222
|
+
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Fault reporting: the result-byte contract from headless-verify.md
|
|
225
|
+
static char fault_code; // 0 none, else a fault number
|
|
226
|
+
|
|
227
|
+
static void fault(char n)
|
|
228
|
+
{
|
|
229
|
+
if (fault_code == 0)
|
|
230
|
+
{
|
|
231
|
+
fault_code = n;
|
|
232
|
+
RESULT = CODE_FAIL;
|
|
233
|
+
vic.color_border = 2;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
#define ASSERT(c, n) do { if (!(c)) fault(n); } while (0)
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Text (screen codes, no KERNAL)
|
|
240
|
+
static void put_str(char row, char col, const char *s)
|
|
241
|
+
{
|
|
242
|
+
char *p = SCREEN + 40 * row + col;
|
|
243
|
+
while (*s)
|
|
244
|
+
{
|
|
245
|
+
char c = *s++;
|
|
246
|
+
*p++ = (c >= 'a' && c <= 'z') ? c - 'a' + 1 : c;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Decimal without division: subtract powers of ten. Worst case 45
|
|
251
|
+
// 16-bit compare-and-subtracts for five digits, far cheaper than five
|
|
252
|
+
// 16-bit divisions (the first build divided and the HUD ate the frame:
|
|
253
|
+
// the budget bar was white end to end while the timed region said 4,000).
|
|
254
|
+
static const unsigned pow10[4] = { 10000, 1000, 100, 10 };
|
|
255
|
+
static void put_dec(char row, char col, unsigned v, char width)
|
|
256
|
+
{
|
|
257
|
+
char *p = SCREEN + 40 * row + col;
|
|
258
|
+
char k = 4 - (width - 1); // first power for this width
|
|
259
|
+
for (; k < 4; k++)
|
|
260
|
+
{
|
|
261
|
+
unsigned d = pow10[k];
|
|
262
|
+
char n = '0';
|
|
263
|
+
while (v >= d) { v -= d; n++; }
|
|
264
|
+
*p++ = n;
|
|
265
|
+
}
|
|
266
|
+
*p = '0' + (char)v;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// PETSCII from the drive to screen codes: digits and punctuation map to
|
|
270
|
+
// themselves, upper-case letters $41..$5A become 1..26.
|
|
271
|
+
static void put_petscii(char row, char col, const char *s, char max)
|
|
272
|
+
{
|
|
273
|
+
char *p = SCREEN + 40 * row + col;
|
|
274
|
+
char i = 0;
|
|
275
|
+
bool ended = false;
|
|
276
|
+
while (i < max)
|
|
277
|
+
{
|
|
278
|
+
char c = ended ? 0 : s[i];
|
|
279
|
+
if (c == 0) { ended = true; c = ' '; }
|
|
280
|
+
else if (c >= 0x41 && c <= 0x5a) c -= 0x40;
|
|
281
|
+
else if (c >= 0x61 && c <= 0x7a) c -= 0x60;
|
|
282
|
+
else if (c < 0x20 || c > 0x5f) c = '?';
|
|
283
|
+
p[i++] = c;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// LFSR (lfsr-random.md): 16-bit Galois, right shift, taps $B400.
|
|
289
|
+
static unsigned rng;
|
|
290
|
+
|
|
291
|
+
static char rnd(void)
|
|
292
|
+
{
|
|
293
|
+
unsigned s = rng;
|
|
294
|
+
char carry = s & 1;
|
|
295
|
+
s >>= 1;
|
|
296
|
+
if (carry) s ^= 0xb400;
|
|
297
|
+
rng = s;
|
|
298
|
+
return (char)s;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
// Tile map (tile-map-render.md RLE, one stream per row)
|
|
303
|
+
static char map[MAP_W * MAP_H];
|
|
304
|
+
static const char tile_code[4] = { 0x20, 0xa0, 0xa0, 0x08 }; // ' ', block, block, 'H'
|
|
305
|
+
static const char tile_colour[4] = { 0, 9, 12, 7 }; // -, brown, grey, yellow
|
|
306
|
+
|
|
307
|
+
static const char *rle_row(const char *src, char *dst, char *len)
|
|
308
|
+
{
|
|
309
|
+
char n = 0;
|
|
310
|
+
for (;;)
|
|
311
|
+
{
|
|
312
|
+
char c = *src++;
|
|
313
|
+
if (c == 0)
|
|
314
|
+
break;
|
|
315
|
+
if (c & 0x80)
|
|
316
|
+
{
|
|
317
|
+
char v = *src++;
|
|
318
|
+
char k = c & 0x7f;
|
|
319
|
+
do { dst[n++] = v; } while (--k);
|
|
320
|
+
}
|
|
321
|
+
else
|
|
322
|
+
{
|
|
323
|
+
do { dst[n++] = *src++; } while (--c);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
*len = n;
|
|
327
|
+
return src;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
static unsigned fold(unsigned chk, unsigned v)
|
|
331
|
+
{
|
|
332
|
+
return (chk ^ v) * 5 + 1;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
static void map_decode_and_draw(void)
|
|
336
|
+
{
|
|
337
|
+
const char *src = map_rle;
|
|
338
|
+
for (char y = 0; y < MAP_H; y++)
|
|
339
|
+
{
|
|
340
|
+
char len;
|
|
341
|
+
char *row = map + y * MAP_W;
|
|
342
|
+
src = rle_row(src, row, &len);
|
|
343
|
+
ASSERT(len == MAP_W, 1);
|
|
344
|
+
unsigned chk = 0;
|
|
345
|
+
for (char x = 0; x < MAP_W; x++)
|
|
346
|
+
chk = fold(chk, row[x]);
|
|
347
|
+
ASSERT(chk == map_row_chk[y], 2);
|
|
348
|
+
char *s = SCREEN + (MAP_ROW + y) * 40;
|
|
349
|
+
char *k = COLOUR + (MAP_ROW + y) * 40;
|
|
350
|
+
for (char x = 0; x < MAP_W; x++)
|
|
351
|
+
{
|
|
352
|
+
char t = row[x];
|
|
353
|
+
s[x] = tile_code[t];
|
|
354
|
+
k[x] = tile_colour[t];
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
ASSERT(src == map_rle + MAP_STREAM_LEN, 3);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Tile at world pixel (wx 0..319, wy 0..199). Outside the map is empty
|
|
361
|
+
// above and solid below/sideways so nothing escapes.
|
|
362
|
+
static char tile_at(int wx, int wy)
|
|
363
|
+
{
|
|
364
|
+
if (wx < 0 || wx >= 320) return T_BRICK;
|
|
365
|
+
int ty = (wy >> 3) - MAP_ROW;
|
|
366
|
+
if (ty < 0) return T_EMPTY;
|
|
367
|
+
if (ty >= MAP_H) return T_BRICK;
|
|
368
|
+
return map[ty * MAP_W + (wx >> 3)];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
static bool solid_at(int wx, int wy)
|
|
372
|
+
{
|
|
373
|
+
char t = tile_at(wx, wy);
|
|
374
|
+
return t == T_BRICK || t == T_PLAT;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
// Player
|
|
379
|
+
// Position: Y is 8.8 in an UNSIGNED 16-bit word (max 200*256 = 51200; a
|
|
380
|
+
// signed int wraps at 128 px, which the fixed-point-jump page warns about).
|
|
381
|
+
// X needs 9 bits of pixel, so it is an int of pixels plus an 8-bit fraction:
|
|
382
|
+
// the walk adds WALK_SPEED to the fraction and carries into the pixel.
|
|
383
|
+
static int px_i; // world px of the sprite's left edge
|
|
384
|
+
static char px_sub; // fraction of a pixel (1/256)
|
|
385
|
+
static unsigned py_fp; // 8.8 world py of the sprite's top
|
|
386
|
+
static int vy_fp; // 8.8, + is down
|
|
387
|
+
static char jump_n; // 0 = not in the table part of a jump
|
|
388
|
+
static bool on_ground, on_ladder;
|
|
389
|
+
static char facing; // 0 right, 1 left
|
|
390
|
+
static char invuln; // frames of invulnerability left
|
|
391
|
+
static char lives;
|
|
392
|
+
static unsigned score, hiscore;
|
|
393
|
+
static bool game_over;
|
|
394
|
+
static char blocked_x; // set when a wall stopped the walk
|
|
395
|
+
|
|
396
|
+
static void player_reset(void)
|
|
397
|
+
{
|
|
398
|
+
px_i = PLAYER_START_X;
|
|
399
|
+
px_sub = 0;
|
|
400
|
+
py_fp = (unsigned)PLAYER_START_Y << 8;
|
|
401
|
+
vy_fp = 0;
|
|
402
|
+
jump_n = 0;
|
|
403
|
+
on_ground = true;
|
|
404
|
+
on_ladder = false;
|
|
405
|
+
facing = 0;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
// Sound (sfx-engine.md): the tune stub writes all three voices every frame;
|
|
410
|
+
// the jump effect borrows voice 2 and re-pokes it after the tune each frame.
|
|
411
|
+
// Nobody has listened to this; it is register-level only.
|
|
412
|
+
struct SfxFrame { unsigned freq; char ctrl; };
|
|
413
|
+
#define SFX_END 0xff
|
|
414
|
+
static const struct SfxFrame jump_frames[] = {
|
|
415
|
+
{ 0x1000, SID_CTRL_TEST | SID_CTRL_GATE },
|
|
416
|
+
{ 0x1200, SID_CTRL_TRI | SID_CTRL_GATE },
|
|
417
|
+
{ 0x1600, SID_CTRL_TRI | SID_CTRL_GATE },
|
|
418
|
+
{ 0x1c00, SID_CTRL_TRI | SID_CTRL_GATE },
|
|
419
|
+
{ 0x2400, SID_CTRL_TRI | SID_CTRL_GATE },
|
|
420
|
+
{ 0x2c00, SID_CTRL_TRI | SID_CTRL_GATE },
|
|
421
|
+
{ 0x3000, SID_CTRL_TRI },
|
|
422
|
+
{ 0x2800, SID_CTRL_TRI },
|
|
423
|
+
{ 0x0000, SFX_END }
|
|
424
|
+
};
|
|
425
|
+
static const struct SfxFrame *sfx_pos; // 0 = voice 2 belongs to the tune
|
|
426
|
+
static unsigned sfx_started;
|
|
427
|
+
static unsigned tune_frame;
|
|
428
|
+
static const unsigned tune_base[3] = { 0x0800, 0x0c00, 0x1200 };
|
|
429
|
+
|
|
430
|
+
static void tune_play(void)
|
|
431
|
+
{
|
|
432
|
+
char step = tune_frame & 31;
|
|
433
|
+
char gate = (tune_frame & 8) ? SID_CTRL_GATE : 0;
|
|
434
|
+
volatile char *r = (volatile char *)0xd400;
|
|
435
|
+
for (char v = 0; v < 3; v++)
|
|
436
|
+
{
|
|
437
|
+
unsigned f = tune_base[v] + (unsigned)step * 16;
|
|
438
|
+
r[0] = (char)f;
|
|
439
|
+
r[1] = f >> 8;
|
|
440
|
+
r[2] = 0x00;
|
|
441
|
+
r[3] = 0x04;
|
|
442
|
+
r[4] = SID_CTRL_TRI | gate;
|
|
443
|
+
r[5] = SID_ATK_8 | SID_DKY_168;
|
|
444
|
+
r[6] = 0x80 | SID_DKY_300;
|
|
445
|
+
r += 7;
|
|
446
|
+
}
|
|
447
|
+
sid.fmodevol = 0x0f;
|
|
448
|
+
tune_frame++;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
static void sfx_jump(void)
|
|
452
|
+
{
|
|
453
|
+
sfx_pos = jump_frames;
|
|
454
|
+
sfx_started++;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
static void sfx_update(void)
|
|
458
|
+
{
|
|
459
|
+
if (!sfx_pos) return;
|
|
460
|
+
if (sfx_pos->ctrl == SFX_END) { sfx_pos = nullptr; return; }
|
|
461
|
+
volatile char *r = (volatile char *)0xd407; // voice 2
|
|
462
|
+
r[5] = SID_ATK_2 | SID_DKY_48;
|
|
463
|
+
r[6] = 0xa0 | SID_DKY_204;
|
|
464
|
+
r[0] = (char)sfx_pos->freq;
|
|
465
|
+
r[1] = sfx_pos->freq >> 8;
|
|
466
|
+
r[2] = 0x00;
|
|
467
|
+
r[3] = 0x08;
|
|
468
|
+
r[4] = sfx_pos->ctrl;
|
|
469
|
+
sfx_pos++;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
// Object pool (object-pool.md): parallel arrays, scan allocator.
|
|
474
|
+
static char obj_state[MAX_OBJ];
|
|
475
|
+
static char obj_type[MAX_OBJ];
|
|
476
|
+
static int obj_x[MAX_OBJ]; // world px, may run past either edge
|
|
477
|
+
static char obj_y[MAX_OBJ]; // world py (sprite top)
|
|
478
|
+
static signed char obj_dx[MAX_OBJ];
|
|
479
|
+
static char obj_anim[MAX_OBJ];
|
|
480
|
+
static unsigned spawned, despawned, spawn_refused;
|
|
481
|
+
|
|
482
|
+
// Wave table: frame offset within a cycle, type (1 slow, 2 fast), count.
|
|
483
|
+
// Rows and directions come from the LFSR so no two cycles look alike.
|
|
484
|
+
struct Wave { unsigned frame; char type; char count; };
|
|
485
|
+
static const struct Wave wave_table[] = {
|
|
486
|
+
{ 30, 1, 1 },
|
|
487
|
+
{ 90, 1, 2 },
|
|
488
|
+
{ 150, 2, 1 },
|
|
489
|
+
{ 210, 1, 2 },
|
|
490
|
+
{ 270, 2, 2 },
|
|
491
|
+
{ 330, 1, 3 },
|
|
492
|
+
{ 390, 2, 2 },
|
|
493
|
+
{ 0xffff, 0, 0 }
|
|
494
|
+
};
|
|
495
|
+
#define WAVE_CYCLE 450
|
|
496
|
+
static char wave_pos;
|
|
497
|
+
static unsigned wave_base;
|
|
498
|
+
|
|
499
|
+
static char pool_alloc(void)
|
|
500
|
+
{
|
|
501
|
+
for (char i = 0; i < MAX_OBJ; i++)
|
|
502
|
+
if (obj_state[i] == OBJ_FREE)
|
|
503
|
+
return i;
|
|
504
|
+
return NO_SLOT;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
static void spawn_enemy(char type)
|
|
508
|
+
{
|
|
509
|
+
char s = pool_alloc();
|
|
510
|
+
if (s == NO_SLOT) { spawn_refused++; return; }
|
|
511
|
+
char r = rnd();
|
|
512
|
+
char row = platform_rows[r % N_PLATFORM_ROWS];
|
|
513
|
+
char speed = (type == 2) ? 2 : 1;
|
|
514
|
+
obj_type[s] = type;
|
|
515
|
+
obj_y[s] = (MAP_ROW + row) * 8 - 21; // feet on the tile top
|
|
516
|
+
if (r & 0x80) { obj_x[s] = -24; obj_dx[s] = speed; }
|
|
517
|
+
else { obj_x[s] = 320; obj_dx[s] = -speed; }
|
|
518
|
+
obj_anim[s] = 0;
|
|
519
|
+
obj_state[s] = OBJ_ALIVE;
|
|
520
|
+
spawned++;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
static void wave_step(unsigned frame)
|
|
524
|
+
{
|
|
525
|
+
unsigned rel = frame - wave_base;
|
|
526
|
+
if (rel >= WAVE_CYCLE)
|
|
527
|
+
{
|
|
528
|
+
wave_base = frame;
|
|
529
|
+
wave_pos = 0;
|
|
530
|
+
rel = 0;
|
|
531
|
+
}
|
|
532
|
+
while (wave_table[wave_pos].frame == rel)
|
|
533
|
+
{
|
|
534
|
+
char n = wave_table[wave_pos].count;
|
|
535
|
+
for (char k = 0; k < n; k++)
|
|
536
|
+
spawn_enemy(wave_table[wave_pos].type);
|
|
537
|
+
wave_pos++;
|
|
538
|
+
ASSERT(wave_pos < 8, 4);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
static void update_enemies(void)
|
|
543
|
+
{
|
|
544
|
+
for (char i = 0; i < MAX_OBJ; i++)
|
|
545
|
+
{
|
|
546
|
+
if (obj_state[i] == OBJ_FREE)
|
|
547
|
+
{
|
|
548
|
+
spr_show(1 + i, false);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
ASSERT(obj_state[i] == OBJ_ALIVE, 5);
|
|
552
|
+
int x = obj_x[i] + obj_dx[i];
|
|
553
|
+
obj_x[i] = x;
|
|
554
|
+
if (x <= -24 || x >= 320)
|
|
555
|
+
{
|
|
556
|
+
obj_state[i] = OBJ_FREE; // left the screen: despawn
|
|
557
|
+
despawned++;
|
|
558
|
+
score += 10; // dodged
|
|
559
|
+
spr_show(1 + i, false);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
obj_anim[i]++;
|
|
563
|
+
spr_image(1 + i, (obj_anim[i] & 8) ? 14 : 15);
|
|
564
|
+
spr_color(1 + i, obj_type[i] == 2 ? VCOL_LT_RED : VCOL_CYAN);
|
|
565
|
+
spr_move(1 + i, x + 24, obj_y[i] + 50);
|
|
566
|
+
spr_show(1 + i, true);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
// Input (joystick-input.md): press events by previous-frame comparison,
|
|
572
|
+
// delayed auto-repeat on the horizontal lines.
|
|
573
|
+
struct JoyEvents { char newp, held, released; };
|
|
574
|
+
|
|
575
|
+
static void joy_edge(char prev, char cur, struct JoyEvents *e)
|
|
576
|
+
{
|
|
577
|
+
char pressed = ~cur & JOY_MASK;
|
|
578
|
+
e->newp = pressed & prev;
|
|
579
|
+
e->held = pressed & ~prev & JOY_MASK;
|
|
580
|
+
e->released = cur & ~prev & JOY_MASK;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Auto-repeat rule: fires on the first frame of a press, again after
|
|
584
|
+
// REPEAT_DELAY frames, then every REPEAT_RATE frames while held. It is
|
|
585
|
+
// used for the ladder step so a held UP climbs one tile per repeat burst
|
|
586
|
+
// rather than sliding; left/right are level-driven (held moves).
|
|
587
|
+
static char repeat_step(char *age, bool pressed)
|
|
588
|
+
{
|
|
589
|
+
if (!pressed) { *age = 0; return 0; }
|
|
590
|
+
char a = *age + 1;
|
|
591
|
+
if (a == REPEAT_DELAY + REPEAT_RATE) a = REPEAT_DELAY;
|
|
592
|
+
*age = a;
|
|
593
|
+
return (a == 1 || a == REPEAT_DELAY) ? 1 : 0;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
#if AUTOPILOT
|
|
597
|
+
// The autopilot: a synthetic active-low port byte from the game's LFSR.
|
|
598
|
+
// It walks in one direction for a random spell, turns round at walls,
|
|
599
|
+
// jumps at random and when an enemy is near on its own row, and climbs
|
|
600
|
+
// when standing at a ladder. Fire is a one-frame pulse so the edge
|
|
601
|
+
// detector sees a press event exactly as a real stick would give one.
|
|
602
|
+
static char ap_dir; // JOY_LEFT or JOY_RIGHT
|
|
603
|
+
static char ap_spell; // frames left in this spell
|
|
604
|
+
static char ap_climb; // frames left climbing
|
|
605
|
+
|
|
606
|
+
static char autopilot_port(void)
|
|
607
|
+
{
|
|
608
|
+
char out = 0xff;
|
|
609
|
+
if (ap_spell == 0 || blocked_x)
|
|
610
|
+
{
|
|
611
|
+
char r = rnd();
|
|
612
|
+
ap_dir = (r & 1) ? JOY_LEFT : JOY_RIGHT;
|
|
613
|
+
if (blocked_x) ap_dir = (ap_dir == JOY_LEFT) ? JOY_RIGHT : JOY_LEFT;
|
|
614
|
+
ap_spell = 30 + (r >> 2);
|
|
615
|
+
}
|
|
616
|
+
ap_spell--;
|
|
617
|
+
int px = px_i;
|
|
618
|
+
int py = (int)(py_fp >> 8);
|
|
619
|
+
bool near = false;
|
|
620
|
+
for (char i = 0; i < MAX_OBJ; i++)
|
|
621
|
+
{
|
|
622
|
+
if (obj_state[i] != OBJ_ALIVE) continue;
|
|
623
|
+
if (obj_y[i] != py) continue;
|
|
624
|
+
int d = obj_x[i] - px;
|
|
625
|
+
if (d < 0) d = -d;
|
|
626
|
+
if (d < 48 && d > 12) near = true;
|
|
627
|
+
}
|
|
628
|
+
char r = rnd();
|
|
629
|
+
if (on_ladder || ap_climb)
|
|
630
|
+
{
|
|
631
|
+
if (ap_climb == 0) ap_climb = 40 + (r & 31);
|
|
632
|
+
ap_climb--;
|
|
633
|
+
out &= ~JOY_UP;
|
|
634
|
+
return out;
|
|
635
|
+
}
|
|
636
|
+
if (tile_at(px + 12, py + BODY_B + 1) == T_LADDER && (r & 3) == 0)
|
|
637
|
+
{
|
|
638
|
+
ap_climb = 40;
|
|
639
|
+
out &= ~JOY_UP;
|
|
640
|
+
return out;
|
|
641
|
+
}
|
|
642
|
+
out &= ~ap_dir;
|
|
643
|
+
if (on_ground && (near || (r & 31) == 0))
|
|
644
|
+
out &= ~JOY_FIRE;
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
647
|
+
#endif
|
|
648
|
+
|
|
649
|
+
// ---------------------------------------------------------------------------
|
|
650
|
+
// Player physics with tile-grid collision (tile-grid-collision.md): the
|
|
651
|
+
// probe points are the body's corners in world pixels.
|
|
652
|
+
static void player_update(char cur, struct JoyEvents *ev, char repeat_fire)
|
|
653
|
+
{
|
|
654
|
+
int px = px_i;
|
|
655
|
+
int py = (int)(py_fp >> 8);
|
|
656
|
+
blocked_x = 0;
|
|
657
|
+
|
|
658
|
+
// Horizontal: level-driven, blocked by a solid at either body corner.
|
|
659
|
+
// Step = WALK_SPEED>>8 whole pixels plus a carry out of the fraction.
|
|
660
|
+
if (!(cur & JOY_LEFT) || !(cur & JOY_RIGHT))
|
|
661
|
+
{
|
|
662
|
+
unsigned t = px_sub + (WALK_SPEED & 0xff);
|
|
663
|
+
int step = (WALK_SPEED >> 8) + (t >> 8);
|
|
664
|
+
int nx = px;
|
|
665
|
+
if (!(cur & JOY_LEFT)) { nx = px - step; facing = 1; }
|
|
666
|
+
else { nx = px + step; facing = 0; }
|
|
667
|
+
int probe = (nx > px) ? nx + BODY_R : nx + BODY_L;
|
|
668
|
+
if (solid_at(probe, py + BODY_T) || solid_at(probe, py + BODY_B))
|
|
669
|
+
blocked_x = 1;
|
|
670
|
+
else
|
|
671
|
+
{
|
|
672
|
+
px_sub = (char)t;
|
|
673
|
+
px_i = nx;
|
|
674
|
+
px = nx;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Ladder: centre column over a ladder tile at mid-body or under the feet.
|
|
679
|
+
int cx = px + 12;
|
|
680
|
+
char mid = tile_at(cx, py + 13);
|
|
681
|
+
char below = tile_at(cx, py + BODY_B + 1);
|
|
682
|
+
on_ladder = (mid == T_LADDER) || (below == T_LADDER && !(cur & JOY_DOWN));
|
|
683
|
+
|
|
684
|
+
if (on_ladder && (!(cur & JOY_UP) || !(cur & JOY_DOWN)))
|
|
685
|
+
{
|
|
686
|
+
vy_fp = 0;
|
|
687
|
+
jump_n = 0;
|
|
688
|
+
if (!(cur & JOY_UP))
|
|
689
|
+
{
|
|
690
|
+
if (tile_at(cx, py + 13 - CLIMB_SPEED) == T_LADDER ||
|
|
691
|
+
tile_at(cx, py + BODY_B) == T_LADDER)
|
|
692
|
+
py_fp -= CLIMB_SPEED << 8;
|
|
693
|
+
}
|
|
694
|
+
else
|
|
695
|
+
{
|
|
696
|
+
if (!solid_at(cx, py + BODY_B + 1 + CLIMB_SPEED))
|
|
697
|
+
py_fp += CLIMB_SPEED << 8;
|
|
698
|
+
}
|
|
699
|
+
py = (int)(py_fp >> 8);
|
|
700
|
+
}
|
|
701
|
+
else
|
|
702
|
+
{
|
|
703
|
+
on_ground = solid_at(px + BODY_L, py + BODY_B + 1) ||
|
|
704
|
+
solid_at(px + BODY_R, py + BODY_B + 1);
|
|
705
|
+
if (on_ladder && mid == T_LADDER) on_ground = true; // hanging still
|
|
706
|
+
|
|
707
|
+
if (on_ground && jump_n == 0 && (ev->newp & JOY_FIRE))
|
|
708
|
+
{
|
|
709
|
+
jump_n = 1;
|
|
710
|
+
vy_fp = jump_vy[0];
|
|
711
|
+
sfx_jump();
|
|
712
|
+
}
|
|
713
|
+
else if (jump_n)
|
|
714
|
+
{
|
|
715
|
+
if (jump_n < JUMP_FRAMES) vy_fp = jump_vy[jump_n++];
|
|
716
|
+
else { vy_fp += GRAVITY; if (vy_fp > MAX_FALL) vy_fp = MAX_FALL; }
|
|
717
|
+
}
|
|
718
|
+
else if (!on_ground)
|
|
719
|
+
{
|
|
720
|
+
vy_fp += GRAVITY;
|
|
721
|
+
if (vy_fp > MAX_FALL) vy_fp = MAX_FALL;
|
|
722
|
+
}
|
|
723
|
+
else
|
|
724
|
+
vy_fp = 0;
|
|
725
|
+
|
|
726
|
+
if (vy_fp)
|
|
727
|
+
{
|
|
728
|
+
py_fp += (unsigned)vy_fp;
|
|
729
|
+
int ny = (int)(py_fp >> 8);
|
|
730
|
+
if (vy_fp > 0)
|
|
731
|
+
{
|
|
732
|
+
// landing: feet crossed into a solid tile -> snap to its top
|
|
733
|
+
if (solid_at(px + BODY_L, ny + BODY_B) || solid_at(px + BODY_R, ny + BODY_B))
|
|
734
|
+
{
|
|
735
|
+
int top = ((ny + BODY_B) & ~7);
|
|
736
|
+
ny = top - BODY_B - 1;
|
|
737
|
+
py_fp = (unsigned)ny << 8;
|
|
738
|
+
vy_fp = 0;
|
|
739
|
+
jump_n = 0;
|
|
740
|
+
on_ground = true;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
else
|
|
744
|
+
{
|
|
745
|
+
// head bump
|
|
746
|
+
if (solid_at(px + BODY_L, ny + BODY_T) || solid_at(px + BODY_R, ny + BODY_T))
|
|
747
|
+
{
|
|
748
|
+
int bottom = ((ny + BODY_T) | 7) + 1;
|
|
749
|
+
ny = bottom - BODY_T;
|
|
750
|
+
py_fp = (unsigned)ny << 8;
|
|
751
|
+
vy_fp = 0;
|
|
752
|
+
jump_n = JUMP_FRAMES; // fall from here
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
py = ny;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
ASSERT(py >= 0 && py < 200, 6);
|
|
760
|
+
ASSERT(px >= 0 && px < 320, 7);
|
|
761
|
+
|
|
762
|
+
(void)repeat_fire;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
static void player_draw(unsigned frame)
|
|
766
|
+
{
|
|
767
|
+
int px = px_i;
|
|
768
|
+
int py = (int)(py_fp >> 8);
|
|
769
|
+
bool show = true;
|
|
770
|
+
if (invuln && (frame & 4)) show = false;
|
|
771
|
+
spr_move(0, px + 24, py + 50);
|
|
772
|
+
spr_color(0, facing ? VCOL_YELLOW : VCOL_WHITE);
|
|
773
|
+
spr_show(0, show);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
static bool hit_enemy(void)
|
|
777
|
+
{
|
|
778
|
+
int px = px_i;
|
|
779
|
+
int py = (int)(py_fp >> 8);
|
|
780
|
+
for (char i = 0; i < MAX_OBJ; i++)
|
|
781
|
+
{
|
|
782
|
+
if (obj_state[i] != OBJ_ALIVE) continue;
|
|
783
|
+
int dx = obj_x[i] - px; if (dx < 0) dx = -dx;
|
|
784
|
+
int dy = (int)obj_y[i] - py; if (dy < 0) dy = -dy;
|
|
785
|
+
if (dx < 8 && dy < 16) return true;
|
|
786
|
+
}
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// ---------------------------------------------------------------------------
|
|
791
|
+
// High score on disk (high-score-persist.md): 5-byte SEQ record, first run
|
|
792
|
+
// gets 62 FILE NOT FOUND and writes; later runs load; replace = scratch+write.
|
|
793
|
+
#define DRIVE 8
|
|
794
|
+
static char hs_rec[5];
|
|
795
|
+
static char hs_back[16];
|
|
796
|
+
static char reply[40];
|
|
797
|
+
static char drive_code;
|
|
798
|
+
static bool drive_ok;
|
|
799
|
+
static bool io_frame; // disk I/O ran this frame: timer B is not ours
|
|
800
|
+
static char disk_state; // screen letter: L loaded, F first, W written, X off
|
|
801
|
+
|
|
802
|
+
static void drive_reply(const char *cmd)
|
|
803
|
+
{
|
|
804
|
+
reply[0] = 0;
|
|
805
|
+
drive_code = 99;
|
|
806
|
+
krnio_setnam(cmd);
|
|
807
|
+
if (krnio_open(15, DRIVE, 15))
|
|
808
|
+
{
|
|
809
|
+
int n = krnio_gets(15, reply, sizeof(reply));
|
|
810
|
+
if (n > 0 && reply[n - 1] == 13) reply[n - 1] = 0;
|
|
811
|
+
if (n >= 2) drive_code = (reply[0] - '0') * 10 + (reply[1] - '0');
|
|
812
|
+
krnio_close(15);
|
|
813
|
+
}
|
|
814
|
+
put_str(DRIVE_ROW, 0, "drive:");
|
|
815
|
+
put_petscii(DRIVE_ROW, 7, reply, 33);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
static int hs_load(void)
|
|
819
|
+
{
|
|
820
|
+
krnio_setnam("HISCORE,S,R");
|
|
821
|
+
bool ok = krnio_open(2, DRIVE, 2);
|
|
822
|
+
if (!ok && (krnio_status() & KRNIO_NODEVICE))
|
|
823
|
+
{
|
|
824
|
+
krnio_close(2);
|
|
825
|
+
put_str(DRIVE_ROW, 0, "drive: no device");
|
|
826
|
+
drive_code = 99;
|
|
827
|
+
drive_ok = false;
|
|
828
|
+
return 0;
|
|
829
|
+
}
|
|
830
|
+
int n = 0;
|
|
831
|
+
if (ok) n = krnio_read(2, hs_back, sizeof(hs_back));
|
|
832
|
+
krnio_close(2);
|
|
833
|
+
drive_reply("");
|
|
834
|
+
return n;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
static bool hs_save(void)
|
|
838
|
+
{
|
|
839
|
+
hs_rec[0] = 'K'; hs_rec[1] = 'B'; hs_rec[2] = 1;
|
|
840
|
+
hs_rec[3] = (char)hiscore; hs_rec[4] = hiscore >> 8;
|
|
841
|
+
krnio_setnam("HISCORE,S,W");
|
|
842
|
+
bool ok = krnio_open(2, DRIVE, 2);
|
|
843
|
+
if (ok) krnio_write(2, hs_rec, 5);
|
|
844
|
+
krnio_close(2);
|
|
845
|
+
drive_reply("");
|
|
846
|
+
return ok && drive_code == 0;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
static void hs_start(void)
|
|
850
|
+
{
|
|
851
|
+
drive_ok = true;
|
|
852
|
+
hiscore = 0;
|
|
853
|
+
int n = hs_load();
|
|
854
|
+
if (drive_code == 0 && n == 5 && hs_back[0] == 'K' && hs_back[1] == 'B' && hs_back[2] == 1)
|
|
855
|
+
{
|
|
856
|
+
hiscore = hs_back[3] | ((unsigned)hs_back[4] << 8);
|
|
857
|
+
disk_state = 'l' - 'a' + 1; // L: loaded
|
|
858
|
+
}
|
|
859
|
+
else if (drive_code == 62)
|
|
860
|
+
{
|
|
861
|
+
disk_state = 'f' - 'a' + 1; // F: first run
|
|
862
|
+
}
|
|
863
|
+
else
|
|
864
|
+
{
|
|
865
|
+
drive_ok = false;
|
|
866
|
+
disk_state = 'x' - 'a' + 1; // X: saving off
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Game over: replace the file (scratch, then write), read it back, show it.
|
|
871
|
+
static unsigned hs_readback;
|
|
872
|
+
static void hs_game_over(void)
|
|
873
|
+
{
|
|
874
|
+
if (!drive_ok) return;
|
|
875
|
+
drive_reply("S0:HISCORE");
|
|
876
|
+
if (hs_save())
|
|
877
|
+
{
|
|
878
|
+
int n = hs_load();
|
|
879
|
+
if (n == 5) hs_readback = hs_back[3] | ((unsigned)hs_back[4] << 8);
|
|
880
|
+
disk_state = 'w' - 'a' + 1; // W: written and read back
|
|
881
|
+
put_str(CYC_ROW, 34, "b"); // read-back, after ERR
|
|
882
|
+
put_dec(CYC_ROW, 35, hs_readback, 5);
|
|
883
|
+
}
|
|
884
|
+
else
|
|
885
|
+
disk_state = 'e' - 'a' + 1; // E: write failed
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// ---------------------------------------------------------------------------
|
|
889
|
+
// Sprites: a small figure for the player, two enemy frames.
|
|
890
|
+
static void make_sprites(void)
|
|
891
|
+
{
|
|
892
|
+
for (char i = 0; i < 64; i++) { SPR_PLAYER[i] = 0; SPR_ENEMY_A[i] = 0; SPR_ENEMY_B[i] = 0; }
|
|
893
|
+
// Player: rows 5..20, byte 1 of each row (x 8..15)
|
|
894
|
+
static const char body[16] = {
|
|
895
|
+
0x3c, 0x7e, 0x5a, 0x7e, 0x3c, 0x18, 0x7e, 0xdb,
|
|
896
|
+
0x99, 0x18, 0x18, 0x3c, 0x24, 0x24, 0x66, 0x66 };
|
|
897
|
+
for (char r = 0; r < 16; r++) SPR_PLAYER[(BODY_T + r) * 3 + 1] = body[r];
|
|
898
|
+
// Enemy A / B: a crab-ish blob, two frames
|
|
899
|
+
static const char ea[16] = {
|
|
900
|
+
0x18, 0x3c, 0x7e, 0xff, 0xdb, 0xff, 0x7e, 0x3c,
|
|
901
|
+
0x5a, 0x99, 0x18, 0x24, 0x42, 0x81, 0x00, 0x00 };
|
|
902
|
+
static const char eb[16] = {
|
|
903
|
+
0x18, 0x3c, 0x7e, 0xff, 0xdb, 0xff, 0x7e, 0x3c,
|
|
904
|
+
0x5a, 0x24, 0x24, 0x42, 0x42, 0x00, 0x00, 0x00 };
|
|
905
|
+
for (char r = 0; r < 16; r++)
|
|
906
|
+
{
|
|
907
|
+
SPR_ENEMY_A[(BODY_T + r) * 3 + 1] = ea[r];
|
|
908
|
+
SPR_ENEMY_B[(BODY_T + r) * 3 + 1] = eb[r];
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// ---------------------------------------------------------------------------
|
|
913
|
+
// Frame sync (frame-sync-loop.md)
|
|
914
|
+
RIRQCode frame_irq;
|
|
915
|
+
volatile char irq_ticks;
|
|
916
|
+
|
|
917
|
+
__interrupt void on_frame(void)
|
|
918
|
+
{
|
|
919
|
+
irq_ticks++;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// CIA1 timer B harness (tile-map-render.md): the KERNAL owns timer A.
|
|
923
|
+
static inline void timer_start(void)
|
|
924
|
+
{
|
|
925
|
+
cia1.crb = 0x00;
|
|
926
|
+
cia1.tb = 0xffff;
|
|
927
|
+
cia1.crb = 0x11;
|
|
928
|
+
}
|
|
929
|
+
static inline unsigned timer_stop(void)
|
|
930
|
+
{
|
|
931
|
+
cia1.crb = 0x00;
|
|
932
|
+
return 0xffff - cia1.tb;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Only fields that changed are rewritten; frame and cyc change every frame.
|
|
936
|
+
static unsigned hud_score = 0xffff, hud_hi = 0xffff, hud_max = 0xffff, hud_drop = 0xffff;
|
|
937
|
+
static char hud_lives = 0xff;
|
|
938
|
+
static void hud_draw(unsigned frame, unsigned cyc, unsigned cyc_max, unsigned dropped)
|
|
939
|
+
{
|
|
940
|
+
if (score != hud_score) { hud_score = score; put_dec(HUD_ROW, 6, score, 5); }
|
|
941
|
+
if (lives != hud_lives) { hud_lives = lives; put_dec(HUD_ROW, 18, lives, 1); }
|
|
942
|
+
if (hiscore != hud_hi) { hud_hi = hiscore; put_dec(HUD_ROW, 23, hiscore, 5); }
|
|
943
|
+
put_dec(HUD_ROW, 31, frame, 5);
|
|
944
|
+
SCREEN[HUD_ROW * 40 + 38] = disk_state;
|
|
945
|
+
put_dec(CYC_ROW, 4, cyc, 5);
|
|
946
|
+
if (cyc_max != hud_max) { hud_max = cyc_max; put_dec(CYC_ROW, 14, cyc_max, 5); }
|
|
947
|
+
if (dropped != hud_drop) { hud_drop = dropped; put_dec(CYC_ROW, 25, dropped, 2); }
|
|
948
|
+
SCREEN[CYC_ROW * 40 + 32] = '0' + fault_code;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// The map draw leaves colour RAM black under an empty tile, so text put
|
|
952
|
+
// over the map needs its own colour: an earlier build drew this black on
|
|
953
|
+
// black and the picture showed nothing.
|
|
954
|
+
#define GAME_OVER_ROW 12
|
|
955
|
+
static void show_game_over(void)
|
|
956
|
+
{
|
|
957
|
+
put_str(GAME_OVER_ROW, 20, "game over");
|
|
958
|
+
for (char i = 0; i < 9; i++) COLOUR[GAME_OVER_ROW * 40 + 20 + i] = VCOL_WHITE;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
static void game_reset(void)
|
|
962
|
+
{
|
|
963
|
+
lives = 3;
|
|
964
|
+
score = 0;
|
|
965
|
+
invuln = 60;
|
|
966
|
+
game_over = false;
|
|
967
|
+
for (char i = 0; i < MAX_OBJ; i++) obj_state[i] = OBJ_FREE;
|
|
968
|
+
player_reset();
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
int main(void)
|
|
972
|
+
{
|
|
973
|
+
for (unsigned i = 0; i < 1000; i++) { SCREEN[i] = 0x20; COLOUR[i] = VCOL_WHITE; }
|
|
974
|
+
vic.color_back = VCOL_BLACK;
|
|
975
|
+
vic.color_border = VCOL_BLACK;
|
|
976
|
+
|
|
977
|
+
put_str(HUD_ROW, 0, "score 00000 lives 3 hi 00000 f 00000 d");
|
|
978
|
+
put_str(CYC_ROW, 0, "cyc 00000 max 00000 drop 00 err 0");
|
|
979
|
+
|
|
980
|
+
map_decode_and_draw();
|
|
981
|
+
make_sprites();
|
|
982
|
+
|
|
983
|
+
// Seed the LFSR from SID voice 3 noise (lfsr-random.md); voice 3 is
|
|
984
|
+
// muted by bit 7 of $D418 so the tune's volume nibble is unaffected.
|
|
985
|
+
sid.voices[2].freq = 0xffff;
|
|
986
|
+
sid.voices[2].ctrl = SID_CTRL_NOISE | SID_CTRL_GATE;
|
|
987
|
+
sid.fmodevol = SID_FMODE_3_OFF | 15;
|
|
988
|
+
rng = sid.random;
|
|
989
|
+
for (char i = 0; i < 50; i++) rnd();
|
|
990
|
+
rng = (rng << 8) | sid.random;
|
|
991
|
+
if (rng == 0) rng = 0xace1;
|
|
992
|
+
|
|
993
|
+
// High score from disk, before the raster IRQ owns the frame. The wait
|
|
994
|
+
// is a workaround: with no wait the first OPEN hung on PAL in VICE and
|
|
995
|
+
// never on NTSC. high-score-persist.md, "A start-up hang seen in VICE,
|
|
996
|
+
// located but not explained", has the monitor trace; fifty frames was
|
|
997
|
+
// enough in every run tried and the smallest wait was not measured.
|
|
998
|
+
#ifndef DISK_WAIT_FRAMES
|
|
999
|
+
#define DISK_WAIT_FRAMES 50
|
|
1000
|
+
#endif
|
|
1001
|
+
for (char i = 0; i < DISK_WAIT_FRAMES; i++) vic_waitFrame();
|
|
1002
|
+
hs_start();
|
|
1003
|
+
|
|
1004
|
+
spr_init(SCREEN);
|
|
1005
|
+
spr_set(0, true, PLAYER_START_X + 24, PLAYER_START_Y + 50, 13, VCOL_WHITE, false, false, false);
|
|
1006
|
+
for (char i = 0; i < MAX_OBJ; i++)
|
|
1007
|
+
spr_set(1 + i, false, 0, 0, 14, VCOL_CYAN, false, false, false);
|
|
1008
|
+
|
|
1009
|
+
game_reset();
|
|
1010
|
+
|
|
1011
|
+
rirq_init(true);
|
|
1012
|
+
rirq_build(&frame_irq, 1);
|
|
1013
|
+
rirq_call(&frame_irq, 0, on_frame);
|
|
1014
|
+
rirq_set(0, SYNC_ROW, &frame_irq);
|
|
1015
|
+
rirq_sort();
|
|
1016
|
+
rirq_start();
|
|
1017
|
+
|
|
1018
|
+
unsigned frame = 0, dropped = 0;
|
|
1019
|
+
unsigned cyc = 0, cyc_max = 0;
|
|
1020
|
+
char seen = irq_ticks;
|
|
1021
|
+
char prev = 0xff;
|
|
1022
|
+
char up_age = 0;
|
|
1023
|
+
bool passed = false, halted = false;
|
|
1024
|
+
struct JoyEvents ev;
|
|
1025
|
+
|
|
1026
|
+
for (;;)
|
|
1027
|
+
{
|
|
1028
|
+
while (irq_ticks == seen)
|
|
1029
|
+
;
|
|
1030
|
+
char delta = irq_ticks - seen;
|
|
1031
|
+
seen += delta;
|
|
1032
|
+
if (delta > 1) dropped += delta - 1;
|
|
1033
|
+
|
|
1034
|
+
#if BUDGET_BAR
|
|
1035
|
+
if (!passed && !fault_code) vic.color_border = VCOL_WHITE;
|
|
1036
|
+
#endif
|
|
1037
|
+
timer_start();
|
|
1038
|
+
|
|
1039
|
+
if (!halted)
|
|
1040
|
+
{
|
|
1041
|
+
// --- input ---
|
|
1042
|
+
#if AUTOPILOT
|
|
1043
|
+
char cur = autopilot_port();
|
|
1044
|
+
#else
|
|
1045
|
+
cia1.ddra = 0xff; cia1.ddrb = 0x00; cia1.pra = 0xff;
|
|
1046
|
+
char cur = cia1.pra; // port 2
|
|
1047
|
+
#endif
|
|
1048
|
+
joy_edge(prev, cur, &ev);
|
|
1049
|
+
prev = cur;
|
|
1050
|
+
char rep = repeat_step(&up_age, !(cur & JOY_UP));
|
|
1051
|
+
|
|
1052
|
+
// --- simulation ---
|
|
1053
|
+
wave_step(frame);
|
|
1054
|
+
update_enemies();
|
|
1055
|
+
player_update(cur, &ev, rep);
|
|
1056
|
+
if (invuln) invuln--;
|
|
1057
|
+
else if (hit_enemy())
|
|
1058
|
+
{
|
|
1059
|
+
lives--;
|
|
1060
|
+
invuln = 90;
|
|
1061
|
+
player_reset();
|
|
1062
|
+
if (lives == 0) game_over = true;
|
|
1063
|
+
}
|
|
1064
|
+
if ((frame & 7) == 0) score++;
|
|
1065
|
+
if (score > hiscore) hiscore = score;
|
|
1066
|
+
|
|
1067
|
+
// --- sound: tune first, then the effect re-pokes voice 2 ---
|
|
1068
|
+
tune_play();
|
|
1069
|
+
sfx_update();
|
|
1070
|
+
|
|
1071
|
+
// --- draw ---
|
|
1072
|
+
player_draw(frame);
|
|
1073
|
+
|
|
1074
|
+
#if AUTOPILOT
|
|
1075
|
+
if (frame == FORCE_OVER && !game_over) { lives = 0; game_over = true; }
|
|
1076
|
+
#endif
|
|
1077
|
+
if (game_over)
|
|
1078
|
+
{
|
|
1079
|
+
for (char i = 0; i < MAX_OBJ; i++) { obj_state[i] = OBJ_FREE; spr_show(1 + i, false); }
|
|
1080
|
+
sid.fmodevol = SID_FMODE_3_OFF; // silence: the tune stops
|
|
1081
|
+
rirq_stop(); // the KERNAL serial code gets the CPU
|
|
1082
|
+
io_frame = true;
|
|
1083
|
+
hs_game_over();
|
|
1084
|
+
rirq_start();
|
|
1085
|
+
seen = irq_ticks;
|
|
1086
|
+
#if AUTOPILOT
|
|
1087
|
+
if (frame >= FORCE_OVER) { halted = true; show_game_over(); }
|
|
1088
|
+
else game_reset();
|
|
1089
|
+
#else
|
|
1090
|
+
game_reset();
|
|
1091
|
+
#endif
|
|
1092
|
+
}
|
|
1093
|
+
frame++;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// The HUD is inside the timed region: cyc is the whole loop body,
|
|
1097
|
+
// shown one frame late (the value on screen is the previous frame's).
|
|
1098
|
+
hud_draw(frame, cyc, cyc_max, dropped);
|
|
1099
|
+
cyc = timer_stop();
|
|
1100
|
+
if (io_frame) io_frame = false; // KERNAL used timer B: discard
|
|
1101
|
+
else if (cyc > cyc_max && frame > 2) cyc_max = cyc;
|
|
1102
|
+
|
|
1103
|
+
#if AUTOPILOT
|
|
1104
|
+
if (frame == PASS_FRAME && !passed && !fault_code)
|
|
1105
|
+
{
|
|
1106
|
+
passed = true;
|
|
1107
|
+
RESULT = CODE_PASS;
|
|
1108
|
+
vic.color_border = 5;
|
|
1109
|
+
}
|
|
1110
|
+
#endif
|
|
1111
|
+
#if BUDGET_BAR
|
|
1112
|
+
if (!passed && !fault_code) vic.color_border = VCOL_BLACK;
|
|
1113
|
+
#endif
|
|
1114
|
+
}
|
|
1115
|
+
return 0;
|
|
1116
|
+
}
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
## Build
|
|
1120
|
+
|
|
1121
|
+
```bash
|
|
1122
|
+
oscar64 -tm=c64 -O2 -o=platformer-scaffold.prg platformer-scaffold.c
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
Outputs `platformer-scaffold.prg` (6,473 bytes), `.map`, `.asm`, `.lbl`.
|
|
1126
|
+
Add `-dAUTOPILOT=0` for a build that reads control port 2 and
|
|
1127
|
+
`-dBUDGET_BAR=0` to leave the border alone.
|
|
1128
|
+
|
|
1129
|
+
Headless, with a fresh disk so the high score has somewhere to go:
|
|
1130
|
+
|
|
1131
|
+
```bash
|
|
1132
|
+
c1541 -format "TEST,01" d64 disk.d64
|
|
1133
|
+
x64sc -default -warp +sound +autostart-delay-random -autostartprgmode 1 \
|
|
1134
|
+
-limitcycles 18000000 -8 disk.d64 -drive8wobbleamplitude 0 -drive8wobblefrequency 0 \
|
|
1135
|
+
-exitscreenshot platformer-scaffold.png -autostart platformer-scaffold.prg
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
To ship this PRG on a bootable D64 and prove the image boots headless, with
|
|
1139
|
+
the score file landing on the release disk, see
|
|
1140
|
+
[release-disk](../../toolchains/release-disk.md).
|
|
1141
|
+
|
|
1142
|
+
## Expected output
|
|
1143
|
+
|
|
1144
|
+
Black screen. Row 0 reads `SCORE 00000 LIVES 3 HI 00000 F 00000 D`
|
|
1145
|
+
followed by one letter for the disk state (`F` first run, `L` loaded,
|
|
1146
|
+
`W` written and read back, `X` saving off, `E` write failed). Row 1 reads
|
|
1147
|
+
`CYC 00000 MAX 00000 DROP 00 ERR 0`: the CIA timer B count for the
|
|
1148
|
+
previous frame's loop body, the largest so far, frames dropped, and the
|
|
1149
|
+
fault number. Rows 2 to 23 are the map: brown brick walls and floor, grey
|
|
1150
|
+
platforms, yellow ladders. Row 24 is the drive's reply to the start-up
|
|
1151
|
+
read: `DRIVE: 62, FILE NOT FOUND,00,00` on a fresh disk, `00, OK,00,00`
|
|
1152
|
+
once a score is on it. The player is an 8 by 16 pixel figure, white
|
|
1153
|
+
facing right and yellow facing left; enemies are cyan (slow) or light
|
|
1154
|
+
red (fast) and walk a platform row from one edge to the other.
|
|
1155
|
+
|
|
1156
|
+
With `AUTOPILOT` on the game plays itself from the LFSR, sets `$02FF` to
|
|
1157
|
+
`01` and the border green at frame 600 if no assert has fired, forces a
|
|
1158
|
+
game over at frame 800, scratches and rewrites `HISCORE` on the disk,
|
|
1159
|
+
reads it back, prints `GAME OVER` in white over the map and `B` plus the
|
|
1160
|
+
read-back value at the end of row 1, and halts.
|
|
1161
|
+
|
|
1162
|
+
Measured in VICE x64sc 3.10 headless at the pinned 18,000,000 cycles on a
|
|
1163
|
+
fresh disk, decoded with the char ROM and PIL. The same command run twice
|
|
1164
|
+
gave byte-identical PNGs on each model.
|
|
1165
|
+
|
|
1166
|
+
| | PAL | NTSC |
|
|
1167
|
+
|---|---|---|
|
|
1168
|
+
| Frame on the HUD | 622 | 711 |
|
|
1169
|
+
| `CYC` (previous frame) | 4,966 | 6,628 |
|
|
1170
|
+
| `MAX` so far | 8,606 | 10,287 |
|
|
1171
|
+
| `DROP` | 00 | 00 |
|
|
1172
|
+
| Score, lives | 148, 3 | 169, 1 |
|
|
1173
|
+
| Drive row | `62, FILE NOT FOUND,00,00` | same |
|
|
1174
|
+
| Border (2, 100) | (98, 213, 50), green | (114, 189, 103), green |
|
|
1175
|
+
| `$02FF` from the `trace store` log | `01` | `01` |
|
|
1176
|
+
| Player, PNG pixels | white, x 212 to 219, y 166 to 181 | yellow (facing left), x 55 to 62, y 191 to 206 |
|
|
1177
|
+
| Cyan enemy pixels | 172, x 68 to 165, y 67 to 176 | 232, x 188 to 315, y 55 to 203 |
|
|
1178
|
+
| Light red enemy | x 316 to 323, y 203 to 215 | x 240 to 247, y 55 to 67 |
|
|
1179
|
+
|
|
1180
|
+
The PNGs are `screenshots/platformer-scaffold.png` and
|
|
1181
|
+
`screenshots/platformer-scaffold-ntsc.png`. Frame 622 is past the
|
|
1182
|
+
frame-600 verdict, so the budget bar is off and the border is green in
|
|
1183
|
+
both pictures. The frame counts differ because the two models run a
|
|
1184
|
+
different number of frames in 18,000,000 cycles, and the scores and lives
|
|
1185
|
+
differ because the LFSR is seeded from SID noise at a cycle that depends
|
|
1186
|
+
on the load.
|
|
1187
|
+
|
|
1188
|
+
At 40,000,000 cycles on the same fresh disk, both models: `F 00801`,
|
|
1189
|
+
`LIVES 0`, `HI 00191`, state `W`, `GAME OVER` on row 12, `B00191` at the
|
|
1190
|
+
end of row 1, `DRIVE: 00, OK,00,00`, `MAX` 8,693 (PAL) and 10,287 (NTSC),
|
|
1191
|
+
`DROP 00`, `$02FF` still `01`. The HUD alone, in the halted state, costs
|
|
1192
|
+
1,605 cycles (PAL) and 1,671 (NTSC). So the loop body runs about 5,000 to
|
|
1193
|
+
7,500 cycles a frame in play with peaks near 10,300 out of 19,656 (PAL)
|
|
1194
|
+
or 17,095 (NTSC), and no frame was dropped in 800.
|
|
1195
|
+
|
|
1196
|
+
Two defects of the build this was promoted from are fixed here and worth
|
|
1197
|
+
knowing about. `GAME OVER` was written over the map with `put_str`, which
|
|
1198
|
+
writes screen codes only; the map draw had left colour RAM black under
|
|
1199
|
+
empty tiles, so the text was black on black and no picture showed it.
|
|
1200
|
+
`show_game_over` sets the colour cells too. And the read-back value was
|
|
1201
|
+
printed at column 27 of row 1, over the `ERR` field; it now follows it.
|
|
1202
|
+
|
|
1203
|
+
## Why this works
|
|
1204
|
+
|
|
1205
|
+
### Tile map and collision
|
|
1206
|
+
|
|
1207
|
+
`map_decode_and_draw` expands one RLE stream per row into `map[]` and
|
|
1208
|
+
writes the screen and colour cells in the same pass. Three asserts guard
|
|
1209
|
+
the data: each row must decode to 40 tiles, must fold to the checksum the
|
|
1210
|
+
generator wrote, and the stream must end where the generator said. That
|
|
1211
|
+
is `tile_map_render` from `docs/techniques/scroll.md`, format and decoder
|
|
1212
|
+
from `docs/recipes/oscar64/tile-map-render.md`.
|
|
1213
|
+
|
|
1214
|
+
Collision reads `map[]`, never screen RAM. `tile_at` takes world pixels,
|
|
1215
|
+
returns empty above the map and solid beyond its other three edges, and
|
|
1216
|
+
`solid_at` treats brick and platform as solid and the ladder as not.
|
|
1217
|
+
`player_update` probes the body's corners: the leading edge's top and
|
|
1218
|
+
bottom before a horizontal step, both feet before a fall, both head
|
|
1219
|
+
corners before a rise. A landing snaps the feet to the top of the tile
|
|
1220
|
+
they entered; a head bump snaps to the tile's bottom and turns the jump
|
|
1221
|
+
into a fall. That is `tile_grid_collision` from `docs/techniques/logic.md`
|
|
1222
|
+
with the listing in `docs/recipes/oscar64/tile-grid-collision.md`.
|
|
1223
|
+
|
|
1224
|
+
### Fixed point and the jump table
|
|
1225
|
+
|
|
1226
|
+
Y is 8.8 in an unsigned 16-bit word. A signed int wraps at 128 pixels,
|
|
1227
|
+
which `docs/recipes/oscar64/fixed-point-jump.md` warns about and the first
|
|
1228
|
+
build hit as fault 6. X needs nine bits of pixel, so it is an int of
|
|
1229
|
+
pixels plus a fraction byte, and the walk adds `WALK_SPEED` to the
|
|
1230
|
+
fraction and carries. The jump is semi-implicit Euler with gravity applied
|
|
1231
|
+
before position, precomputed into `jump_vy[]` by the generator from
|
|
1232
|
+
`V0 = -3.5` px/frame and `G = 0.15625` px/frame²; after the table ends
|
|
1233
|
+
gravity continues so walking off an edge falls. `fixed_point_8_8` and
|
|
1234
|
+
`jump_arc_table` are in `docs/techniques/maths.md`.
|
|
1235
|
+
|
|
1236
|
+
### Joystick events and auto-repeat
|
|
1237
|
+
|
|
1238
|
+
`joy_edge` compares this frame's active-low port byte with the previous
|
|
1239
|
+
one and yields new, held and released masks. `repeat_step` fires on the
|
|
1240
|
+
first frame of a press, again at 20 frames held, then every 4. Left and
|
|
1241
|
+
right are level-driven; the repeat runs on UP and is available for a
|
|
1242
|
+
ladder step. `autopilot_port` synthesises the port byte from the LFSR and
|
|
1243
|
+
feeds the same `joy_edge`, so no code path differs after the read. The
|
|
1244
|
+
listings are from `docs/recipes/oscar64/joystick-input.md`;
|
|
1245
|
+
`joystick_edge_detect` and `joystick_autorepeat` are in
|
|
1246
|
+
`docs/techniques/input.md`.
|
|
1247
|
+
|
|
1248
|
+
### Object pool and LFSR
|
|
1249
|
+
|
|
1250
|
+
Six enemy slots in parallel arrays, a scan allocator that refuses when
|
|
1251
|
+
full and counts the refusal, a wave table walked once per frame against
|
|
1252
|
+
a 450-frame cycle, and despawn when an enemy leaves either edge. Rows and
|
|
1253
|
+
directions come from `rnd`, a 16-bit Galois LFSR with taps `$B400` seeded
|
|
1254
|
+
from SID voice 3 noise with voice 3 muted by bit 7 of `$D418`. That is
|
|
1255
|
+
`docs/recipes/oscar64/object-pool.md` and
|
|
1256
|
+
`docs/recipes/oscar64/lfsr-random.md`; `object_pool` is in
|
|
1257
|
+
`docs/techniques/logic.md` and `lfsr_random` in `docs/techniques/maths.md`.
|
|
1258
|
+
|
|
1259
|
+
### Frame loop, timer and budget bar
|
|
1260
|
+
|
|
1261
|
+
A raster IRQ at row 250 increments a tick byte; the loop waits for it to
|
|
1262
|
+
change and counts any change greater than one as dropped frames. CIA1
|
|
1263
|
+
timer B, not A, brackets the whole loop body including the HUD, because
|
|
1264
|
+
the KERNAL owns timer A for its jiffy clock. A frame in which the KERNAL
|
|
1265
|
+
did disk I/O is discarded from `MAX`: the KERNAL's serial routines use
|
|
1266
|
+
timer B while a file is open, and before the exclusion went in the
|
|
1267
|
+
original build showed `MAX 64256` after the first read. The border is white from the tick to the end of the
|
|
1268
|
+
body while `BUDGET_BAR` is on, so the picture shows the cost as raster
|
|
1269
|
+
lines beside the timer's number. `frame_sync_loop` is in
|
|
1270
|
+
`docs/techniques/raster.md`, the listing in
|
|
1271
|
+
`docs/recipes/oscar64/frame-sync-loop.md`, and the timer harness in
|
|
1272
|
+
`docs/recipes/oscar64/tile-map-render.md`.
|
|
1273
|
+
|
|
1274
|
+
### Decimal HUD
|
|
1275
|
+
|
|
1276
|
+
`put_dec` subtracts powers of ten, at most 45 sixteen-bit compares for
|
|
1277
|
+
five digits, and only fields that changed are rewritten. The first build
|
|
1278
|
+
used five divisions per field and thirty per frame, and the budget bar
|
|
1279
|
+
was white end to end while the timed region said 4,000 cycles, which is
|
|
1280
|
+
what the bar is for. `decimal_print` is in `docs/techniques/text.md` and
|
|
1281
|
+
`docs/recipes/oscar64/print-number.md`.
|
|
1282
|
+
|
|
1283
|
+
### Sound
|
|
1284
|
+
|
|
1285
|
+
`tune_play` writes all three voices every frame from a 32-step pattern;
|
|
1286
|
+
`sfx_update` runs after it and re-pokes voice 2 while the jump effect is
|
|
1287
|
+
playing, so the tune never sees the effect and the effect always wins.
|
|
1288
|
+
Nobody has listened to either; the work is register-level, and the tune
|
|
1289
|
+
runs at frame rate so it is a fifth faster on NTSC. That is
|
|
1290
|
+
`sfx_engine_beside_music` in `docs/techniques/music-sid.md` and
|
|
1291
|
+
`docs/recipes/oscar64/sfx-engine.md`.
|
|
1292
|
+
|
|
1293
|
+
### High score on disk
|
|
1294
|
+
|
|
1295
|
+
At start `hs_load` opens `HISCORE,S,R`, reads five bytes, closes, and
|
|
1296
|
+
reads the error channel; 62 means a first run, 00 with a valid record
|
|
1297
|
+
means a loaded score, anything else turns saving off. If OPEN fails with
|
|
1298
|
+
`KRNIO_NODEVICE` the row says `no device` and nothing else touches the
|
|
1299
|
+
bus. At game over `hs_game_over` scratches, writes, and reads back, with
|
|
1300
|
+
`rirq_stop` around the I/O so the KERNAL's serial timing is undisturbed.
|
|
1301
|
+
The three `file-io` techniques `kernal_file_write_seq`,
|
|
1302
|
+
`kernal_file_read_seq` and `error_channel_check` are in
|
|
1303
|
+
`docs/techniques/file-io.md`; the listing is
|
|
1304
|
+
`docs/recipes/oscar64/high-score-persist.md`.
|
|
1305
|
+
|
|
1306
|
+
`DISK_WAIT_FRAMES` is a workaround and the page says so. The original
|
|
1307
|
+
build with no wait before the first OPEN hung on PAL under `-autostart`
|
|
1308
|
+
with a true drive, never on NTSC; fifty `vic_waitFrame` calls removed it,
|
|
1309
|
+
and so did an unrelated change of code size. The monitor trace is in
|
|
1310
|
+
`docs/recipes/oscar64/high-score-persist.md`, section "A start-up hang
|
|
1311
|
+
seen in VICE, located but not explained": the C64 spins at `$EDD6`
|
|
1312
|
+
waiting for the drive to become the talker after TKSA, and the drive
|
|
1313
|
+
stayed a listener. Why it misses the release is not measured, the smallest
|
|
1314
|
+
wait that suffices is not measured, and no real 1541 was tried. Keep the
|
|
1315
|
+
wait until one of those is.
|
|
1316
|
+
|
|
1317
|
+
### The generator
|
|
1318
|
+
|
|
1319
|
+
The RLE stream, the row checksums, the jump table and the platform row
|
|
1320
|
+
list in the listing came from this script. It is not built by the
|
|
1321
|
+
listing gate. Run it, then paste its output over the block marked
|
|
1322
|
+
"Generated tables" in the source.
|
|
1323
|
+
|
|
1324
|
+
```text
|
|
1325
|
+
#!/usr/bin/env python3
|
|
1326
|
+
"""Generate map.h: the RLE-compressed tile map, its per-row checksums, the
|
|
1327
|
+
jump velocity table, and the wave/row tables. Run: python3 gen.py > map.h
|
|
1328
|
+
|
|
1329
|
+
RLE format is the KB tile-map-render recipe's: control byte c, c&0x80 = run of
|
|
1330
|
+
(c&0x7f) copies of the next byte, c<0x80 = c literal bytes follow, 0 = end of
|
|
1331
|
+
row. One stream per row, no offset table."""
|
|
1332
|
+
|
|
1333
|
+
# 40 x 22 tiles on screen rows 2..23. '.' empty, '#' brick (solid),
|
|
1334
|
+
# '=' platform (solid), 'H' ladder (climbable, not solid).
|
|
1335
|
+
ART = [
|
|
1336
|
+
"#......................................#", # 0 (screen row 2)
|
|
1337
|
+
"#......................................#", # 1
|
|
1338
|
+
"#......................................#", # 2
|
|
1339
|
+
"#......................................#", # 3
|
|
1340
|
+
"#..=========............===========....#", # 4
|
|
1341
|
+
"#..........H...........................#", # 5
|
|
1342
|
+
"#..........H...........................#", # 6
|
|
1343
|
+
"#..........H...........................#", # 7
|
|
1344
|
+
"#....==========H=========.....=======..#", # 8
|
|
1345
|
+
"#..............H.......................#", # 9
|
|
1346
|
+
"#..............H.......................#", # 10
|
|
1347
|
+
"#..............H.......................#", # 11
|
|
1348
|
+
"#=========.....H....===========........#", # 12
|
|
1349
|
+
"#......................H..............##", # 13
|
|
1350
|
+
"#......................H...............#", # 14
|
|
1351
|
+
"#......................H...............#", # 15
|
|
1352
|
+
"#...==============.....H....=========..#", # 16
|
|
1353
|
+
"#......................H...............#", # 17
|
|
1354
|
+
"#......................H...............#", # 18
|
|
1355
|
+
"#......................H...............#", # 19
|
|
1356
|
+
"#......................H...............#", # 20
|
|
1357
|
+
"########################################", # 21
|
|
1358
|
+
]
|
|
1359
|
+
CODE = {'.': 0, '#': 1, '=': 2, 'H': 3}
|
|
1360
|
+
W, H = 40, len(ART)
|
|
1361
|
+
assert all(len(r) == W for r in ART), "every row must be 40 wide"
|
|
1362
|
+
assert H == 22
|
|
1363
|
+
|
|
1364
|
+
|
|
1365
|
+
def rle_row(row):
|
|
1366
|
+
out = []
|
|
1367
|
+
i = 0
|
|
1368
|
+
while i < len(row):
|
|
1369
|
+
j = i
|
|
1370
|
+
while j < len(row) and row[j] == row[i] and j - i < 127:
|
|
1371
|
+
j += 1
|
|
1372
|
+
n = j - i
|
|
1373
|
+
if n >= 2:
|
|
1374
|
+
out += [0x80 | n, row[i]]
|
|
1375
|
+
i = j
|
|
1376
|
+
else:
|
|
1377
|
+
# literal run: collect until a run of >= 2 starts
|
|
1378
|
+
k = i
|
|
1379
|
+
while k < len(row) and (k + 1 >= len(row) or row[k + 1] != row[k]) and k - i < 127:
|
|
1380
|
+
k += 1
|
|
1381
|
+
if k == i:
|
|
1382
|
+
k = i + 1
|
|
1383
|
+
out += [k - i] + row[i:k]
|
|
1384
|
+
i = k
|
|
1385
|
+
out.append(0)
|
|
1386
|
+
return out
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
def fold(chk, v):
|
|
1390
|
+
return ((chk ^ v) * 5 + 1) & 0xffff
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
rows = [[CODE[c] for c in r] for r in ART]
|
|
1394
|
+
stream = []
|
|
1395
|
+
checks = []
|
|
1396
|
+
for r in rows:
|
|
1397
|
+
s = rle_row(r)
|
|
1398
|
+
# verify decode
|
|
1399
|
+
dec = []
|
|
1400
|
+
i = 0
|
|
1401
|
+
while True:
|
|
1402
|
+
c = s[i]; i += 1
|
|
1403
|
+
if c == 0:
|
|
1404
|
+
break
|
|
1405
|
+
if c & 0x80:
|
|
1406
|
+
dec += [s[i]] * (c & 0x7f); i += 1
|
|
1407
|
+
else:
|
|
1408
|
+
dec += s[i:i + c]; i += c
|
|
1409
|
+
assert dec == r, (dec, r)
|
|
1410
|
+
chk = 0
|
|
1411
|
+
for v in r:
|
|
1412
|
+
chk = fold(chk, v)
|
|
1413
|
+
checks.append(chk)
|
|
1414
|
+
stream += s
|
|
1415
|
+
|
|
1416
|
+
# Jump table: 8.8 velocity per jump frame, semi-implicit Euler like the KB
|
|
1417
|
+
# fixed-point-jump recipe (gravity first, then position).
|
|
1418
|
+
V0 = -0x0380 # -3.5 px/frame
|
|
1419
|
+
G = 0x0028 # +0.15625 px/frame^2
|
|
1420
|
+
vy = V0
|
|
1421
|
+
table = []
|
|
1422
|
+
while True:
|
|
1423
|
+
vy += G
|
|
1424
|
+
table.append(vy)
|
|
1425
|
+
if vy >= -V0:
|
|
1426
|
+
break
|
|
1427
|
+
|
|
1428
|
+
# Wave table: frame offset (within a cycle), type, count, row-choice list index.
|
|
1429
|
+
# Enemy rows are the platform rows an enemy walks along (feet on the tile top).
|
|
1430
|
+
PLATFORM_ROWS = [4, 8, 12, 16, 21]
|
|
1431
|
+
|
|
1432
|
+
print("// Generated by gen.py — do not edit by hand.")
|
|
1433
|
+
print("#define MAP_W %d" % W)
|
|
1434
|
+
print("#define MAP_H %d" % H)
|
|
1435
|
+
print("#define MAP_STREAM_LEN %d" % len(stream))
|
|
1436
|
+
print("static const char map_rle[] = {")
|
|
1437
|
+
for i in range(0, len(stream), 16):
|
|
1438
|
+
print(" " + ", ".join("0x%02x" % b for b in stream[i:i + 16]) + ",")
|
|
1439
|
+
print("};")
|
|
1440
|
+
print("static const unsigned map_row_chk[MAP_H] = {")
|
|
1441
|
+
print(" " + ", ".join("0x%04x" % c for c in checks))
|
|
1442
|
+
print("};")
|
|
1443
|
+
print("#define JUMP_FRAMES %d" % len(table))
|
|
1444
|
+
print("static const int jump_vy[JUMP_FRAMES] = {")
|
|
1445
|
+
for i in range(0, len(table), 8):
|
|
1446
|
+
print(" " + ", ".join("%d" % v for v in table[i:i + 8]) + ",")
|
|
1447
|
+
print("};")
|
|
1448
|
+
print("#define N_PLATFORM_ROWS %d" % len(PLATFORM_ROWS))
|
|
1449
|
+
print("static const char platform_rows[N_PLATFORM_ROWS] = { %s };" % ", ".join(str(r) for r in PLATFORM_ROWS))
|
|
1450
|
+
import sys
|
|
1451
|
+
print("// map bytes %d -> rle %d bytes; jump apex %.1f px over %d frames" % (
|
|
1452
|
+
W * H, len(stream), sum(-v for v in table if v < 0) / 256.0, len(table)), file=sys.stderr)
|
|
1453
|
+
```
|
|
1454
|
+
|
|
1455
|
+
## Where each part came from
|
|
1456
|
+
|
|
1457
|
+
The build this page promotes logged every KB call it made. The parts and
|
|
1458
|
+
their sources: tile map from `tile-map-render.md`; collision from the
|
|
1459
|
+
`tile_grid_collision` prose, now with `tile-grid-collision.md` beside it;
|
|
1460
|
+
jump from `fixed-point-jump.md`; input from `joystick-input.md`; pool
|
|
1461
|
+
and waves from `object-pool.md`; random from `lfsr-random.md`; frame loop
|
|
1462
|
+
and bar from `frame-sync-loop.md`; decimal from the recipe listings,
|
|
1463
|
+
rewritten to subtract; sound from `sfx-engine.md`; disk from
|
|
1464
|
+
`high-score-persist.md` and `save-load-seq-file.md`; verdict from
|
|
1465
|
+
`headless-verify.md` and `runtime/vice-reference.md`, "Verifying a run
|
|
1466
|
+
without a human". The autopilot had no source in the KB and is this
|
|
1467
|
+
page's own.
|