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,1524 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: maths
|
|
3
|
+
chip: 6510
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- doc-type: technique-reference -->
|
|
7
|
+
|
|
8
|
+
# Maths: fixed point, table multiply, jump arcs
|
|
9
|
+
|
|
10
|
+
The 6510 has no multiply, no divide and no fraction. Game movement that
|
|
11
|
+
needs sub-pixel speeds or acceleration is done with fixed-point integers
|
|
12
|
+
and lookup tables. Every cycle figure on this page was measured in VICE
|
|
13
|
+
x64sc 3.10 with CIA1 timer B unless it says otherwise. The measuring
|
|
14
|
+
program is at the end of the page.
|
|
15
|
+
|
|
16
|
+
## fixed_point_8_8 — 8.8 fixed-point positions and velocities
|
|
17
|
+
|
|
18
|
+
**Complexity:** low
|
|
19
|
+
**Region:** both
|
|
20
|
+
**Uses registers:** (none)
|
|
21
|
+
|
|
22
|
+
### Why
|
|
23
|
+
|
|
24
|
+
A sprite register takes a whole pixel. A jump that accelerates by an
|
|
25
|
+
eighth of a pixel per frame cannot be expressed in whole pixels, and
|
|
26
|
+
rounding each frame drifts. Keeping eight extra bits of fraction gives
|
|
27
|
+
speeds down to 1/256 pixel per frame with no drift, at the cost of one
|
|
28
|
+
extra byte per coordinate and one extra add per update.
|
|
29
|
+
|
|
30
|
+
### How
|
|
31
|
+
|
|
32
|
+
A value is a 16-bit two's complement integer read as `whole * 256 +
|
|
33
|
+
fraction`. `$0100` is 1.0, `$0080` is 0.5, `$FFC0` is -0.25. The high
|
|
34
|
+
byte is the whole part and the low byte the fraction. Nothing on the CPU
|
|
35
|
+
knows this; the programmer does.
|
|
36
|
+
|
|
37
|
+
Adding two 8.8 values is a plain 16-bit add, low byte first, and the
|
|
38
|
+
carry moves the fraction overflow into the whole part:
|
|
39
|
+
|
|
40
|
+
```asm
|
|
41
|
+
clc
|
|
42
|
+
lda pos_lo
|
|
43
|
+
adc vel_lo
|
|
44
|
+
sta pos_lo
|
|
45
|
+
lda pos_hi
|
|
46
|
+
adc vel_hi
|
|
47
|
+
sta pos_hi
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Measured: 26 cycles with all six operands in absolute memory (100 calls
|
|
51
|
+
timed, 7,504 cycles less the 4,904 of the empty loop, display off so no
|
|
52
|
+
badline stole a cycle). With zero-page operands the same sequence is 20
|
|
53
|
+
cycles by the instruction table (rung 3, not measured here).
|
|
54
|
+
|
|
55
|
+
The pixel byte for a sprite register is the high byte, `pos_hi`. In
|
|
56
|
+
Oscar64 that is `(char)(y_fp >> 8)`; the compiler emits a byte move, not
|
|
57
|
+
eight shifts.
|
|
58
|
+
|
|
59
|
+
Signed values need no special add. A negative velocity is just a large
|
|
60
|
+
unsigned 16-bit number and the carry does the right thing. The sign
|
|
61
|
+
lives in bit 15, so `bmi` after `lda pos_hi` tests it, and a signed
|
|
62
|
+
comparison against a limit uses `sec / sbc / bvc / eor #$80` on the high
|
|
63
|
+
byte or, in C, an `int` compare. Where the whole part must stay
|
|
64
|
+
unsigned (a screen Y of 0 to 255) keep the position in an `unsigned`
|
|
65
|
+
and the velocity in an `int`; two's complement makes the add of the two
|
|
66
|
+
correct without a cast on the 6510.
|
|
67
|
+
|
|
68
|
+
### 16.8 when the range needs it
|
|
69
|
+
|
|
70
|
+
8.8 in a signed 16-bit word covers -128.00 to +127.99. A world
|
|
71
|
+
coordinate in a scrolling level, or a screen Y above 127 that must also
|
|
72
|
+
go negative, does not fit. 16.8 uses three bytes: two whole, one
|
|
73
|
+
fraction. The add gains one more `lda / adc / sta` (four cycles each
|
|
74
|
+
absolute, three zero page, rung 3). The pixel byte is the middle byte;
|
|
75
|
+
the top byte is the scroll or the sign. `c64-game-archetypes.md` names
|
|
76
|
+
8.8 and 16.8 as the standard for the scrolling platformer.
|
|
77
|
+
|
|
78
|
+
### Exhaustive check
|
|
79
|
+
|
|
80
|
+
The measuring program adds every pair `a = i * $0101`, `b = j * $0101`
|
|
81
|
+
for `i, j` in 0..255: 65,536 pairs, every low-byte pair once, with the
|
|
82
|
+
high-byte pair `(i, j)` added under the carry that low-byte pair
|
|
83
|
+
produces. Each 16-bit result is folded into a checksum (rotate left one,
|
|
84
|
+
exclusive-or the result, add 13) and compared with the compiler's own
|
|
85
|
+
`add_a + add_b`. Result on screen, PAL, 70,000,000 cycles:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
add cs 8aa1 exp 8aa1 miss 00000
|
|
89
|
+
pass
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Python over the same 65,536 pairs gives `0x8AA1`. Zero mismatches. A
|
|
93
|
+
first version of the harness ran two more sweeps meant to cover the
|
|
94
|
+
high-byte pairs under a forced carry of 0 and of 1; they left the
|
|
95
|
+
checksum unchanged, which means they did not run as written, so they
|
|
96
|
+
were removed and are not claimed here.
|
|
97
|
+
|
|
98
|
+
### Recipes
|
|
99
|
+
|
|
100
|
+
- `recipes/oscar64/fixed-point-jump.md`
|
|
101
|
+
|
|
102
|
+
## table_multiply_8x8 — Quarter-square table multiply
|
|
103
|
+
|
|
104
|
+
**Complexity:** medium
|
|
105
|
+
**Region:** both
|
|
106
|
+
**Uses registers:** (none)
|
|
107
|
+
**Cost:** cycles_per_frame=52, bytes_data=2048
|
|
108
|
+
**Cost basis:** arithmetic
|
|
109
|
+
|
|
110
|
+
### Why
|
|
111
|
+
|
|
112
|
+
A shift-and-add 8x8 multiply is a loop of eight iterations, around 100
|
|
113
|
+
to 150 cycles depending on the routine (rung 4, not measured here). A
|
|
114
|
+
table gives the product in a fixed 52 cycles. `effects-vector-3d.md`
|
|
115
|
+
budgets six table multiplies per rotated point on that basis.
|
|
116
|
+
|
|
117
|
+
### How
|
|
118
|
+
|
|
119
|
+
The identity is `a * b = q(a + b) - q(a - b)` with `q(n) = floor(n^2 / 4)`.
|
|
120
|
+
When `a + b` is odd, `a - b` is odd too and the two floors lose the same
|
|
121
|
+
quarter, so the result is exact.
|
|
122
|
+
|
|
123
|
+
Two tables of `q` are kept, each 512 entries split into a low-byte and a
|
|
124
|
+
high-byte table: `sqr[i] = q(i)` for `i` in 0..511, and `nsq[i] =
|
|
125
|
+
q(|i - 255|)`. That is 2,048 bytes, built at start-up in a loop of 512
|
|
126
|
+
steps using `q(i + 1) = q(i) + floor((i + 1) / 2)`, so no multiply is
|
|
127
|
+
needed to build the multiply table. Each table starts on a page
|
|
128
|
+
boundary.
|
|
129
|
+
|
|
130
|
+
The routine patches `a` into the low address byte of two reads into
|
|
131
|
+
`sqr` and `255 - a` (which is `a eor $ff`) into two reads into `nsq`,
|
|
132
|
+
then indexes all four with `Y = b`. `sqr + a` indexed by `b` is
|
|
133
|
+
`q(a + b)`; `nsq + 255 - a` indexed by `b` is `q(|b - a|)`.
|
|
134
|
+
|
|
135
|
+
```asm
|
|
136
|
+
lda a
|
|
137
|
+
sta m1 + 1
|
|
138
|
+
sta m2 + 1
|
|
139
|
+
eor #$ff
|
|
140
|
+
sta m3 + 1
|
|
141
|
+
sta m4 + 1
|
|
142
|
+
ldy b
|
|
143
|
+
sec
|
|
144
|
+
m1: lda sqr_lo,y
|
|
145
|
+
m3: sbc nsq_lo,y
|
|
146
|
+
sta result
|
|
147
|
+
m2: lda sqr_hi,y
|
|
148
|
+
m4: sbc nsq_hi,y
|
|
149
|
+
sta result + 1
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Cycle budget
|
|
153
|
+
|
|
154
|
+
Measured: 52 cycles for `a = b = 0` with `a`, `b` and `result` in
|
|
155
|
+
absolute memory (10,104 cycles for 100 calls less the 4,904 empty-loop
|
|
156
|
+
baseline; the call and return are in both figures and cancel). That is
|
|
157
|
+
exactly the instruction-table sum: six absolute stores and loads at 4,
|
|
158
|
+
one `eor #` and one `sec` at 2, four indexed reads at 4 and two stores
|
|
159
|
+
at 4. Each indexed read adds one cycle when `base + a + b` crosses a
|
|
160
|
+
page, so the worst case is 56 (rung 3). With `a`, `b` and the result in
|
|
161
|
+
zero page the fixed part drops by 4 to 48 (rung 3, not measured here);
|
|
162
|
+
only the two operand loads and the two result stores move, because the
|
|
163
|
+
four patch stores write into the routine's own code and stay absolute.
|
|
164
|
+
The page-aligned tables are what keep the patch to one byte: without
|
|
165
|
+
alignment the high address byte would need a carry as well.
|
|
166
|
+
|
|
167
|
+
### The optimiser will move the patched instructions
|
|
168
|
+
|
|
169
|
+
In Oscar64 the block above must be written `__asm volatile`. Without
|
|
170
|
+
`volatile` the assembler optimiser at `-O2` rewrote `ldy / lda ,y` as
|
|
171
|
+
`ldx / lda ,x`, duplicated the block, and left the four `sta m1 + 1`
|
|
172
|
+
stores pointing into the other copy. The running copy keeps the
|
|
173
|
+
operands it was assembled with, so every product came out as
|
|
174
|
+
`q(b) - q(|b - 255|)`, which is `$C080` for `0 * 0`, the figure the
|
|
175
|
+
first version of the harness on this page read (an earlier version of
|
|
176
|
+
this sentence gave the formula as `q(a + b) - q(b)`, which is 0 for
|
|
177
|
+
`0 * 0` and does not fit that reading; the executed sequence is quoted
|
|
178
|
+
in `pitfalls/cpu.md#asm_optimiser_moves_self_modified_instruction`).
|
|
179
|
+
`toolchains/oscar64-reference.md` (the assembler
|
|
180
|
+
optimizer paragraph) documents the switch; the symptom is recorded here because the compiled
|
|
181
|
+
listing looked right in the copy that was inspected first.
|
|
182
|
+
|
|
183
|
+
### Signed variant
|
|
184
|
+
|
|
185
|
+
For signed 8-bit operands, run the unsigned routine on the raw bytes and
|
|
186
|
+
correct: if `a` is negative subtract `b` from the high byte, if `b` is
|
|
187
|
+
negative subtract `a` from the high byte. That is two tests and at most
|
|
188
|
+
two `sbc` on the high byte (rung 3, not measured here). The other common
|
|
189
|
+
form biases both operands by 128 and uses a table built for the biased
|
|
190
|
+
range; it needs no correction but a different table.
|
|
191
|
+
|
|
192
|
+
### Exhaustive check
|
|
193
|
+
|
|
194
|
+
All 65,536 `(a, b)` pairs, `a` outer and `b` inner, each product folded
|
|
195
|
+
into the same checksum as the add and compared with Oscar64's own
|
|
196
|
+
`(unsigned)a * b`. Result on screen, PAL:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
mul cs 0f09 exp 0f09 miss 00000
|
|
200
|
+
pass
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Python over the same pairs in the same order gives `0x0F09`. Zero
|
|
204
|
+
mismatches.
|
|
205
|
+
|
|
206
|
+
### Recipes
|
|
207
|
+
|
|
208
|
+
- No recipe yet. The tables and the checking harness are the measuring
|
|
209
|
+
program at the end of this page; `recipes/oscar64/fixed-point-jump.md`
|
|
210
|
+
does not use a multiply.
|
|
211
|
+
|
|
212
|
+
## division_8_16bit — Shift-and-subtract division, reciprocals and divide by ten
|
|
213
|
+
|
|
214
|
+
**Complexity:** low
|
|
215
|
+
**Region:** both
|
|
216
|
+
**Uses registers:** (none)
|
|
217
|
+
**Cost:** cycles_per_frame=791
|
|
218
|
+
**Cost basis:** measured-vice
|
|
219
|
+
**Cost measured on:** oscar64-divide-check (one call, worst case 65535/1)
|
|
220
|
+
|
|
221
|
+
### Why
|
|
222
|
+
|
|
223
|
+
The 6510 has no divide. A game needs one for a tile coordinate from a
|
|
224
|
+
pixel position when the tile is not a power of two, for an average, a
|
|
225
|
+
percentage, a speed from a distance and a time, and for splitting a
|
|
226
|
+
number into decimal digits. A binary shift-and-subtract loop does any
|
|
227
|
+
of these in a few hundred cycles; a divisor that is known at assembly
|
|
228
|
+
time can be turned into a multiply or a table and costs a tenth of
|
|
229
|
+
that. The figures here were measured with CIA1 timer A in
|
|
230
|
+
`recipes/oscar64/divide-check.md` (rung 1, VICE x64sc 3.10), with the
|
|
231
|
+
operands in absolute memory; the zero-page and constant-divisor
|
|
232
|
+
figures are instruction-table arithmetic from those loops (rung 3).
|
|
233
|
+
|
|
234
|
+
### How
|
|
235
|
+
|
|
236
|
+
Long division in binary. Clear the remainder. Repeat once per dividend
|
|
237
|
+
bit: shift the dividend left, shifting its top bit into the bottom of
|
|
238
|
+
the remainder; if the remainder is now at least the divisor, subtract
|
|
239
|
+
the divisor from it and set the bit of the dividend that the shift
|
|
240
|
+
vacated. When the passes are done the dividend holds the quotient and
|
|
241
|
+
the remainder is the remainder. The loop for a 16-bit dividend and an
|
|
242
|
+
8-bit divisor, quotient in `dvd`, remainder in `rem`:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
lda #0
|
|
246
|
+
sta rem
|
|
247
|
+
ldx #16
|
|
248
|
+
loop: asl dvd
|
|
249
|
+
rol dvd+1
|
|
250
|
+
rol rem ; ninth bit of the remainder lands in C
|
|
251
|
+
lda rem
|
|
252
|
+
bcs sub ; a nine-bit remainder always exceeds the divisor
|
|
253
|
+
cmp dvs
|
|
254
|
+
bcc next ; remainder < divisor: quotient bit stays 0
|
|
255
|
+
sub: sbc dvs ; C is set on both paths, so sbc is exact
|
|
256
|
+
sta rem
|
|
257
|
+
inc dvd ; the bit asl just vacated becomes 1
|
|
258
|
+
next: dex
|
|
259
|
+
bne loop
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The 8/8 form drops `rol dvd+1` and runs eight passes. The 16/16 form
|
|
263
|
+
keeps a two-byte remainder, catches its 17th bit with the same `bcs`
|
|
264
|
+
after the second `rol`, compares high bytes first and falls through to
|
|
265
|
+
the low bytes only when they are equal, and subtracts two bytes.
|
|
266
|
+
|
|
267
|
+
The remainder is one bit wider than the divisor before the subtract:
|
|
268
|
+
it was less than the divisor, and it has just been doubled plus one.
|
|
269
|
+
That bit is the carry out of `rol rem`. Taking `bcs` straight to `sbc`
|
|
270
|
+
handles it, because a remainder with that bit set is at least any
|
|
271
|
+
divisor of the narrower width, and because `rol` left the carry set,
|
|
272
|
+
which is the state `sbc` needs. The bit can only appear once the loop
|
|
273
|
+
has consumed more dividend bits than the divisor is wide, so it is a
|
|
274
|
+
16/8 hazard: without the `bcs` that loop is wrong for divisors of `$81`
|
|
275
|
+
and above (24,400 misses over the recipe's 65,536 dividends; `256 / 255`
|
|
276
|
+
comes out as 0 remainder 0), while the 8/8 and 16/16 loops pass their
|
|
277
|
+
sweeps with the guard deleted, because an 8-bit dividend runs out of
|
|
278
|
+
passes before its remainder can reach `$80` and a 16-bit divisor would
|
|
279
|
+
need a seventeenth pass. An earlier version of this paragraph said the
|
|
280
|
+
guardless loop was wrong "for every divisor of `$80` (8-bit) or `$8000`
|
|
281
|
+
(16-bit) and above"; the measurement is in
|
|
282
|
+
`pitfalls/maths.md#division_loop_missing_ninth_bit_guard`.
|
|
283
|
+
|
|
284
|
+
A divisor of zero is not trapped. Every compare succeeds, so the
|
|
285
|
+
quotient comes out as all ones and the remainder as the dividend (its
|
|
286
|
+
low byte in the 16/8 form; rung 3, from the loop; not run). Test for
|
|
287
|
+
it before the call if the divisor can be zero.
|
|
288
|
+
|
|
289
|
+
Signed operands: divide the absolute values, negate the quotient if
|
|
290
|
+
the signs differed, and give the remainder the sign of the dividend.
|
|
291
|
+
The tests and negations are about 30 cycles on top of the unsigned
|
|
292
|
+
loop (rung 3, not measured here).
|
|
293
|
+
|
|
294
|
+
### Division by a constant
|
|
295
|
+
|
|
296
|
+
A divisor fixed at assembly time is a multiply by its reciprocal. Pick
|
|
297
|
+
a shift `k` and the multiplier `m = ceil(2^k / d)`; then
|
|
298
|
+
`q = (n * m) >> k` equals `n / d` for every `n` with `n * e < 2^k`,
|
|
299
|
+
where `e = m * d - 2^k` is the rounding error of the reciprocal, which
|
|
300
|
+
lies between 0 and `d - 1`. For ten, `m = 205` with `k = 11` has
|
|
301
|
+
`e = 2`, so the quotient is exact for `n` up to 1023; Python over all
|
|
302
|
+
of them agrees. `m = $CCCD` with `k = 19` has `e = 2` as well and is
|
|
303
|
+
exact for every 16-bit `n`, but the product is 32 bits wide. `6554`
|
|
304
|
+
with `k = 16` has `e = 4` and is exact only below 16,384: the first
|
|
305
|
+
wrong value is 16,389. Get the remainder as `n - q * d`.
|
|
306
|
+
|
|
307
|
+
For an 8-bit `n` the product `n * 205` is an 8x8 multiply, and
|
|
308
|
+
`table_multiply_8x8` above delivers it in 52 cycles; three `lsr` of
|
|
309
|
+
the high byte give the quotient, about 65 cycles with the loads
|
|
310
|
+
(rung 3, not measured here). The multiply then needs its 2 KB of
|
|
311
|
+
tables. A form with no multiply and no table does the same job in
|
|
312
|
+
shifts and adds and was measured:
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
lda n
|
|
316
|
+
lsr
|
|
317
|
+
sta q ; n/2
|
|
318
|
+
lsr
|
|
319
|
+
clc
|
|
320
|
+
adc q ; n/2 + n/4 = 0.75 n
|
|
321
|
+
sta q
|
|
322
|
+
lsr
|
|
323
|
+
lsr
|
|
324
|
+
lsr
|
|
325
|
+
lsr
|
|
326
|
+
clc
|
|
327
|
+
adc q ; 0.75 n + 0.75 n / 16 = 0.797 n
|
|
328
|
+
lsr
|
|
329
|
+
lsr
|
|
330
|
+
lsr ; 0.0996 n, equal to n/10 or one below it
|
|
331
|
+
sta q
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Then `r = n - 10 * q` is 0 to 11 (Python over 0..255; the recipe
|
|
335
|
+
checks every value), and one `cmp #10 / sbc #10 / inc q` fixes it.
|
|
336
|
+
79 cycles for quotient and remainder, any `n`, measured. The 16-bit
|
|
337
|
+
version of the same series adds a `q + q >> 8` term, leaves a
|
|
338
|
+
remainder of 0 to 13, and needs one fix as well (rung 3, Python; not
|
|
339
|
+
run on the machine).
|
|
340
|
+
|
|
341
|
+
### Division by ten and the decimal print
|
|
342
|
+
|
|
343
|
+
Splitting a 16-bit number into five digits by repeated 16/8 division
|
|
344
|
+
by ten costs four calls of the loop above, 2,793 cycles in the recipe
|
|
345
|
+
for 65,535 against 936 for the subtract-powers route that
|
|
346
|
+
`decimal_print` (`techniques/text.md`) uses on the same value, three
|
|
347
|
+
times as much. Moving the operands to zero page and writing the
|
|
348
|
+
divisor as an immediate brings a subtracting pass down from 49 to 39
|
|
349
|
+
cycles and a pass without a subtract from 36 to 30 (rung 3), which by
|
|
350
|
+
the same proportion puts the four calls near 2,200, still more than
|
|
351
|
+
double. The divide route gives the units digit first, which is what a
|
|
352
|
+
right-to-left field wants; the subtract route gives the most
|
|
353
|
+
significant digit first and is cheaper. A removed divide by ten should
|
|
354
|
+
be replaced with subtract-powers or a table, not left out.
|
|
355
|
+
|
|
356
|
+
### When a table wins
|
|
357
|
+
|
|
358
|
+
An 8-bit dividend and a constant divisor is a 256-byte table, or two
|
|
359
|
+
for quotient and remainder; the recipe's `div10_tab` reads both in 20
|
|
360
|
+
cycles, measured, and the tables build in a counting loop at start
|
|
361
|
+
with no divide. A tile column from a pixel X with 8-pixel tiles is a
|
|
362
|
+
shift, not a divide, and with 12 or 20-pixel tiles it is this table.
|
|
363
|
+
A variable 8-bit divisor cannot be a direct table (64 KB); the usual
|
|
364
|
+
compromise is a 256-byte table of `256 / d` and a multiply by it,
|
|
365
|
+
which is one less than the true quotient for some inputs and needs a
|
|
366
|
+
correction step (rung 4, not measured here).
|
|
367
|
+
|
|
368
|
+
### Cycle budget
|
|
369
|
+
|
|
370
|
+
Measured, body only, operands in absolute memory:
|
|
371
|
+
|
|
372
|
+
| Route | Cycles | Case |
|
|
373
|
+
|---|---|---|
|
|
374
|
+
| 8/8 loop | 351 | 255/1, a subtract on every pass |
|
|
375
|
+
| 8/8 loop | 260 | 255/255, one subtract |
|
|
376
|
+
| 16/8 loop | 791 | 65535/1, worst case |
|
|
377
|
+
| 16/8 loop | 674 | 65535/10 |
|
|
378
|
+
| 16/16 loop | 1,339 | 65535/1, worst case |
|
|
379
|
+
| 16/16 loop | 715 | 65535/$8000 |
|
|
380
|
+
| by 10, shift-add | 79 | any 8-bit value |
|
|
381
|
+
| by 10, two tables | 20 | any 8-bit value |
|
|
382
|
+
| five digits by four 16/8 divides | 2,793 | 65535 |
|
|
383
|
+
| five digits by subtract-powers | 936 | 65535 |
|
|
384
|
+
|
|
385
|
+
The loop figures are the instruction-table sums: a 16/8 pass with a
|
|
386
|
+
subtract is 49 cycles with the operands absolute (`asl`, `rol`, `rol`
|
|
387
|
+
and `inc` at 6, the loads, compare and stores at 4, the branches and
|
|
388
|
+
`dex` at 2 or 3), 36 without one, and 8 cycles of setup with the
|
|
389
|
+
remainder in absolute memory (7 in zero page); 65535/1 is
|
|
390
|
+
`8 + 16 * 49 - 1`, and the 8/8 figures close the same way: 255/1 is
|
|
391
|
+
`8 + 8 * 43 - 1` and 255/255 is `8 + 7 * 30 + 43 - 1`. In zero page the
|
|
392
|
+
same pass is 41 and 31, so the range is 502 to 662 for 16/8 and 214 to
|
|
393
|
+
294 for 8/8 (rung 3). An immediate divisor takes 1 off the compare and
|
|
394
|
+
1 off the subtract in zero page, so a pass there is 30 without a
|
|
395
|
+
subtract and 39 with one, or 36 when the `bcs` path was taken. The
|
|
396
|
+
Cost line
|
|
397
|
+
carries the measured 16/8 worst case; a routine called once a frame is
|
|
398
|
+
about 4 per cent of a PAL frame in its slowest form (791 of 19,656).
|
|
399
|
+
|
|
400
|
+
### Recipes
|
|
401
|
+
|
|
402
|
+
- `recipes/oscar64/divide-check.md` — the three loops, the shift-add
|
|
403
|
+
and table divide by ten, checked over every 8-bit pair and a 16-bit
|
|
404
|
+
sweep against Oscar64's `/` and `%` and against Python, with the
|
|
405
|
+
cycle harness on screen.
|
|
406
|
+
|
|
407
|
+
## jump_arc_table — Gravity in 8.8 and a precomputed arc
|
|
408
|
+
|
|
409
|
+
**Complexity:** low
|
|
410
|
+
**Region:** both
|
|
411
|
+
**Uses registers:** (none)
|
|
412
|
+
**Requires:** fixed_point_8_8
|
|
413
|
+
|
|
414
|
+
### Why
|
|
415
|
+
|
|
416
|
+
A jump is a launch velocity and a constant downward acceleration. Done
|
|
417
|
+
in 8.8, each frame is two adds. Done as a table, each frame is one
|
|
418
|
+
indexed load, and the height at every frame is known before the game
|
|
419
|
+
runs, which is what a level designer needs to place a platform.
|
|
420
|
+
|
|
421
|
+
### How
|
|
422
|
+
|
|
423
|
+
Per frame, in this order: velocity gains gravity, then position gains
|
|
424
|
+
velocity, then the pixel byte goes to the sprite register. With a
|
|
425
|
+
launch of `-$0400` (4.0 pixels per frame upward) and gravity `+$0020`
|
|
426
|
+
(0.125 pixels per frame per frame) the sprite is back on the ground
|
|
427
|
+
after 63 updates; the apex is 62 pixels up and lasts frames 28 to 35.
|
|
428
|
+
These numbers come from running the update in Python and were confirmed
|
|
429
|
+
by the recipe, which runs the same update on the 6510 every frame and
|
|
430
|
+
turns the border red if the live value ever differs from the table (it
|
|
431
|
+
stayed green through 252 PAL and 283 NTSC frames in the pinned runs).
|
|
432
|
+
|
|
433
|
+
Update order matters. Gravity-then-position (semi-implicit Euler)
|
|
434
|
+
lands exactly at update 63 with these constants. Position-then-gravity
|
|
435
|
+
moves the full launch speed on the first step, lands after 65 updates
|
|
436
|
+
and differs from jump frame 4 onward (rung 3, from the same Python
|
|
437
|
+
integration). Pick one and generate the table from the same code.
|
|
438
|
+
|
|
439
|
+
Apex: the frame where the velocity crosses zero, here `$0400 / $0020 =
|
|
440
|
+
32` updates; the table's flat top is wider than one frame because the
|
|
441
|
+
position moves less than a pixel per frame there. Landing: the first
|
|
442
|
+
update where the position reaches or passes the ground. With these
|
|
443
|
+
constants the 63 velocity steps sum to exactly zero, so the landing is
|
|
444
|
+
exact and the clamp does nothing; keep the clamp for constants where
|
|
445
|
+
the last step would carry the sprite below the ground.
|
|
446
|
+
|
|
447
|
+
### The table
|
|
448
|
+
|
|
449
|
+
63 bytes of sprite Y, one per jump frame, generated off-line by the same
|
|
450
|
+
two adds. The recipe embeds it as `arc_y[]`. A game that stores only the
|
|
451
|
+
table needs no fixed point at all for the jump; it needs the table
|
|
452
|
+
length and a frame index.
|
|
453
|
+
|
|
454
|
+
### Divide by a constant
|
|
455
|
+
|
|
456
|
+
Converting 8.8 to pixels is a divide by 256, which is taking the high
|
|
457
|
+
byte. Halving a velocity is `cmp #$80 / ror hi / ror lo` (arithmetic
|
|
458
|
+
shift right of a 16-bit signed value, 2 + 5 + 5 = 12 cycles zero page,
|
|
459
|
+
rung 3). A divide by a constant that is not a power of two is a multiply
|
|
460
|
+
by its reciprocal in 8.8 using `table_multiply_8x8`, then the high byte:
|
|
461
|
+
`v / 3` is `v * $55 >> 8`, which is one part in 256 low because
|
|
462
|
+
`3 * $55 = 255`, so the result is up to one pixel short over `0..255`
|
|
463
|
+
(`255 * $55 >> 8` is 84, not 85). Use `$56` and accept an error the
|
|
464
|
+
other way, or a 16-bit reciprocal, if that matters (rung 3, checked in
|
|
465
|
+
Python over 0..255, not measured on the 6510).
|
|
466
|
+
|
|
467
|
+
### Region
|
|
468
|
+
|
|
469
|
+
The table is in frames. On NTSC the same table plays about 20 percent
|
|
470
|
+
faster in wall-clock time (60 frames per second against 50);
|
|
471
|
+
`game-design-patterns.md` notes that gravity and speeds are tuned per
|
|
472
|
+
region for that reason. The recipe keeps one table and shows the frame
|
|
473
|
+
number so the two pictures can be compared frame for frame.
|
|
474
|
+
|
|
475
|
+
### Variations
|
|
476
|
+
|
|
477
|
+
Velocity table, launch from any height. `arc_y[]` is absolute sprite Y
|
|
478
|
+
from one fixed ground, so a platformer cannot play it from a platform.
|
|
479
|
+
Store the per-frame velocity instead, `vy_tab[n] = V0 + (n + 1) * G`
|
|
480
|
+
clamped to a terminal velocity, and do `y += vy_tab[n]` from whatever
|
|
481
|
+
`y` the jump starts at; the caller decides what a landing is (a floor
|
|
482
|
+
under the sprite, a tile lookup). With `-$0380`, `+$0024` and terminal
|
|
483
|
+
`+$0210` the 52 entries sum to exactly zero, so a sprite that launches
|
|
484
|
+
from a floor is back on it exactly, and the terminal run at the end of
|
|
485
|
+
the table is what a longer fall holds. `recipes/oscar64/fixed-point-jump-velocity.md`
|
|
486
|
+
runs two sprites from floors 64 pixels apart off one table and checks
|
|
487
|
+
the exact landing on the 6510 every jump; the constants were picked in
|
|
488
|
+
Python for the zero sum, and the pair `-3.5` with `+$28` does not give
|
|
489
|
+
one for any terminal velocity from 1.5 to 4.0 in steps of 1/32.
|
|
490
|
+
|
|
491
|
+
### Recipes
|
|
492
|
+
|
|
493
|
+
- `recipes/oscar64/fixed-point-jump.md`
|
|
494
|
+
- `recipes/oscar64/fixed-point-jump-velocity.md`
|
|
495
|
+
|
|
496
|
+
### The measuring program
|
|
497
|
+
|
|
498
|
+
This section belongs to all three techniques above; it sits under the
|
|
499
|
+
last one only because the extractor reads every H2 as a technique. Not
|
|
500
|
+
built by the listing gate (technique pages are not compiled); it was
|
|
501
|
+
built and run by hand with the commands below. Both the multiply and the
|
|
502
|
+
add are `__noinline` functions whose body is `__asm volatile`, so the
|
|
503
|
+
timed and checked code is the instruction sequence shown above and not
|
|
504
|
+
the compiler's version of it.
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
oscar64 -tm=c64 -O2 -o=fpcheck.prg fpcheck.c
|
|
508
|
+
GSETTINGS_SCHEMA_DIR=/opt/homebrew/share/glib-2.0/schemas timeout 120 x64sc -default -warp +sound +autostart-delay-random -autostartprgmode 1 -limitcycles 70000000 -exitscreenshot fpcheck.png -autostart fpcheck.prg
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Screen after the run, decoded from the PNG with the character ROM:
|
|
512
|
+
|
|
513
|
+
```
|
|
514
|
+
mul cs 0f09 exp 0f09 miss 00000
|
|
515
|
+
add cs 8aa1 exp 8aa1 miss 00000
|
|
516
|
+
|
|
517
|
+
cyc x100 empty 04904 mul 10104 add 07504
|
|
518
|
+
|
|
519
|
+
pass
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Timing is CIA1 timer B, force-loaded from `$FFFF`, counting phi2, read
|
|
523
|
+
after 100 calls; `$D011` has DEN clear during the timed loops so no
|
|
524
|
+
badline or sprite fetch stalls the CPU (with the display on, the same
|
|
525
|
+
loops read 5,205 to 5,472 for the empty case depending on where the
|
|
526
|
+
loop sat relative to the badlines).
|
|
527
|
+
|
|
528
|
+
```c
|
|
529
|
+
// fpcheck.c -- exhaustive check of the quarter-square multiply and the
|
|
530
|
+
// 8.8 add, plus a CIA timer B cycle measurement of each core.
|
|
531
|
+
#include <c64/vic.h>
|
|
532
|
+
#include <c64/cia.h>
|
|
533
|
+
|
|
534
|
+
#define SCREEN ((char *)0x0400)
|
|
535
|
+
#define COLOUR ((char *)0xd800)
|
|
536
|
+
|
|
537
|
+
#define EXPECT_MUL 0x0F09 // from the Python fold over all 65,536 products
|
|
538
|
+
#define EXPECT_ADD 0x8AA1 // from the Python fold over all 65,536 sums
|
|
539
|
+
|
|
540
|
+
char sqr_lo[512], sqr_hi[512], nsq_lo[512], nsq_hi[512];
|
|
541
|
+
#pragma align(sqr_lo, 256)
|
|
542
|
+
#pragma align(sqr_hi, 256)
|
|
543
|
+
#pragma align(nsq_lo, 256)
|
|
544
|
+
#pragma align(nsq_hi, 256)
|
|
545
|
+
|
|
546
|
+
char mul_a, mul_b;
|
|
547
|
+
unsigned mul_r;
|
|
548
|
+
int add_a, add_b, add_r;
|
|
549
|
+
|
|
550
|
+
static void build_tables(void)
|
|
551
|
+
{
|
|
552
|
+
unsigned q = 0;
|
|
553
|
+
for (unsigned i = 0; i < 512; i++)
|
|
554
|
+
{
|
|
555
|
+
sqr_lo[i] = q & 0xff;
|
|
556
|
+
sqr_hi[i] = q >> 8;
|
|
557
|
+
q += (i + 1) >> 1; // q(i+1) = q(i) + floor((i+1)/2)
|
|
558
|
+
}
|
|
559
|
+
for (unsigned i = 0; i < 512; i++)
|
|
560
|
+
{
|
|
561
|
+
unsigned k = i < 255 ? 255 - i : i - 255;
|
|
562
|
+
nsq_lo[i] = sqr_lo[k];
|
|
563
|
+
nsq_hi[i] = sqr_hi[k];
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
__noinline void qmul(void)
|
|
568
|
+
{
|
|
569
|
+
__asm volatile
|
|
570
|
+
{
|
|
571
|
+
lda mul_a
|
|
572
|
+
sta m1 + 1
|
|
573
|
+
sta m2 + 1
|
|
574
|
+
eor #$ff
|
|
575
|
+
sta m3 + 1
|
|
576
|
+
sta m4 + 1
|
|
577
|
+
ldy mul_b
|
|
578
|
+
sec
|
|
579
|
+
m1: lda sqr_lo, y
|
|
580
|
+
m3: sbc nsq_lo, y
|
|
581
|
+
sta mul_r
|
|
582
|
+
m2: lda sqr_hi, y
|
|
583
|
+
m4: sbc nsq_hi, y
|
|
584
|
+
sta mul_r + 1
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
__noinline void fpadd(void)
|
|
589
|
+
{
|
|
590
|
+
__asm volatile
|
|
591
|
+
{
|
|
592
|
+
clc
|
|
593
|
+
lda add_a
|
|
594
|
+
adc add_b
|
|
595
|
+
sta add_r
|
|
596
|
+
lda add_a + 1
|
|
597
|
+
adc add_b + 1
|
|
598
|
+
sta add_r + 1
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
static inline unsigned fold(unsigned cs, unsigned r)
|
|
603
|
+
{
|
|
604
|
+
cs = (cs << 1) | (cs & 0x8000 ? 1 : 0);
|
|
605
|
+
cs ^= r;
|
|
606
|
+
return cs + 0x0d;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
static const char hexg[16] = { 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,1,2,3,4,5,6 };
|
|
610
|
+
|
|
611
|
+
static void put_hex(char row, char col, unsigned v)
|
|
612
|
+
{
|
|
613
|
+
char *p = SCREEN + 40 * row + col;
|
|
614
|
+
p[0] = hexg[(v >> 12) & 15]; p[1] = hexg[(v >> 8) & 15];
|
|
615
|
+
p[2] = hexg[(v >> 4) & 15]; p[3] = hexg[v & 15];
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
static void put_dec(char row, char col, unsigned v)
|
|
619
|
+
{
|
|
620
|
+
char *p = SCREEN + 40 * row + col;
|
|
621
|
+
for (signed char i = 4; i >= 0; i--) { p[i] = 0x30 + v % 10; v /= 10; }
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
static void put_str(char row, char col, const char *s)
|
|
625
|
+
{
|
|
626
|
+
char *p = SCREEN + 40 * row + col;
|
|
627
|
+
while (*s) { char c = *s++; p++[0] = (c >= 'a' && c <= 'z') ? c - 'a' + 1 : (c == ' ' ? 0x20 : c); }
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
volatile char sink;
|
|
631
|
+
|
|
632
|
+
static unsigned time_loop(void (*fn)(void))
|
|
633
|
+
{
|
|
634
|
+
cia1.crb = 0x00;
|
|
635
|
+
cia1.tb = 0xffff;
|
|
636
|
+
cia1.crb = 0x11; // start, force load, count phi2
|
|
637
|
+
for (char i = 0; i < 100; i++) { fn(); sink = i; }
|
|
638
|
+
cia1.crb = 0x00;
|
|
639
|
+
return 0xffff - cia1.tb;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
void nothing(void) { }
|
|
643
|
+
|
|
644
|
+
int main(void)
|
|
645
|
+
{
|
|
646
|
+
for (unsigned i = 0; i < 1000; i++) { SCREEN[i] = 0x20; COLOUR[i] = 1; }
|
|
647
|
+
vic.color_back = 0; vic.color_border = 0;
|
|
648
|
+
__asm { sei }
|
|
649
|
+
|
|
650
|
+
build_tables();
|
|
651
|
+
|
|
652
|
+
unsigned cs = 0, miss = 0;
|
|
653
|
+
mul_a = 0;
|
|
654
|
+
do {
|
|
655
|
+
mul_b = 0;
|
|
656
|
+
do {
|
|
657
|
+
qmul();
|
|
658
|
+
if (mul_r != (unsigned)mul_a * mul_b) miss++;
|
|
659
|
+
cs = fold(cs, mul_r);
|
|
660
|
+
} while (++mul_b);
|
|
661
|
+
} while (++mul_a);
|
|
662
|
+
put_str(0, 0, "mul cs exp miss");
|
|
663
|
+
put_hex(0, 7, cs); put_hex(0, 16, EXPECT_MUL); put_dec(0, 26, miss);
|
|
664
|
+
bool ok = (cs == EXPECT_MUL) && miss == 0;
|
|
665
|
+
|
|
666
|
+
cs = 0; miss = 0;
|
|
667
|
+
unsigned i = 0;
|
|
668
|
+
do {
|
|
669
|
+
unsigned j = 0;
|
|
670
|
+
do {
|
|
671
|
+
add_a = i * 0x101; add_b = j * 0x101; fpadd();
|
|
672
|
+
if (add_r != add_a + add_b) miss++;
|
|
673
|
+
cs = fold(cs, add_r);
|
|
674
|
+
} while (++j < 256);
|
|
675
|
+
} while (++i < 256);
|
|
676
|
+
put_str(1, 0, "add cs exp miss");
|
|
677
|
+
put_hex(1, 7, cs); put_hex(1, 16, EXPECT_ADD); put_dec(1, 26, miss);
|
|
678
|
+
ok = ok && (cs == EXPECT_ADD) && miss == 0;
|
|
679
|
+
|
|
680
|
+
vic.ctrl1 = 0x0b; // DEN off: no badlines while timing
|
|
681
|
+
vic_waitFrame();
|
|
682
|
+
unsigned t0 = time_loop(nothing);
|
|
683
|
+
unsigned tm = time_loop(qmul);
|
|
684
|
+
unsigned ta = time_loop(fpadd);
|
|
685
|
+
vic.ctrl1 = 0x1b;
|
|
686
|
+
put_str(3, 0, "cyc x100 empty mul add");
|
|
687
|
+
put_dec(3, 15, t0); put_dec(3, 25, tm); put_dec(3, 35, ta);
|
|
688
|
+
|
|
689
|
+
put_str(5, 0, ok ? "pass" : "fail");
|
|
690
|
+
vic.color_border = ok ? 5 : 2;
|
|
691
|
+
for (;;)
|
|
692
|
+
;
|
|
693
|
+
return 0;
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
The Python side of the checksum:
|
|
698
|
+
|
|
699
|
+
```python
|
|
700
|
+
def fold(cs, r):
|
|
701
|
+
cs = ((cs << 1) | (cs >> 15)) & 0xFFFF
|
|
702
|
+
cs ^= r & 0xFFFF
|
|
703
|
+
return (cs + 0x0D) & 0xFFFF
|
|
704
|
+
|
|
705
|
+
cs = 0
|
|
706
|
+
for a in range(256):
|
|
707
|
+
for b in range(256):
|
|
708
|
+
cs = fold(cs, a * b) # -> 0x0F09
|
|
709
|
+
cs = 0
|
|
710
|
+
for i in range(256):
|
|
711
|
+
for j in range(256):
|
|
712
|
+
cs = fold(cs, (i * 0x101 + j * 0x101) & 0xFFFF) # -> 0x8AA1
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
A plain rotate-and-xor fold was tried first and gave `0x0000` for the
|
|
716
|
+
add sweep: the domain is symmetric in `i` and `j` and the xor cancelled.
|
|
717
|
+
The `+ 13` breaks that. sim6502 was not used; `dotnet` runs on the
|
|
718
|
+
machine but the harness above answers the question in one VICE run and
|
|
719
|
+
its result is a screen the character ROM can decode.
|
|
720
|
+
|
|
721
|
+
## lfsr_random — Linear-feedback shift register random numbers
|
|
722
|
+
|
|
723
|
+
**Complexity:** low
|
|
724
|
+
**Region:** both
|
|
725
|
+
**Uses registers:** D41B, D412, D40E, D40F, D418, DC04, DC05, DC0E
|
|
726
|
+
**Cost:** cycles_per_frame=14, bytes_code=1947
|
|
727
|
+
**Cost basis:** arithmetic
|
|
728
|
+
**Cost measured on:** oscar64-lfsr-random (one 8-bit step, screen blanked; bytes are the whole PRG)
|
|
729
|
+
**Claims:** sid_voice_3 (init), sid_voice_3_readback (init), sid_filter_volume (init), cia1_timer_a (reads)
|
|
730
|
+
**Claims basis:** derived-listing
|
|
731
|
+
|
|
732
|
+
### Why
|
|
733
|
+
|
|
734
|
+
A game needs cheap pseudo-random bytes for spawn positions, noise
|
|
735
|
+
pixels and starfields, and it needs the sequence to differ from one
|
|
736
|
+
play to the next. A linear-feedback shift register (LFSR) gives the
|
|
737
|
+
bytes in a dozen cycles with no table. The seed is what makes each game
|
|
738
|
+
different, and the C64 has three sources for it: SID voice 3 noise, a
|
|
739
|
+
CIA timer, and the moment the player first touches the controls.
|
|
740
|
+
|
|
741
|
+
### How
|
|
742
|
+
|
|
743
|
+
Galois form, shifting right. Shift the state right one bit; if the bit
|
|
744
|
+
that fell out was 1, XOR the tap mask into the state. The state after
|
|
745
|
+
each step is the output.
|
|
746
|
+
|
|
747
|
+
- 8-bit: taps `$B8` (x^8 + x^6 + x^5 + x^4 + 1). Period measured at 255
|
|
748
|
+
from the three seeds the recipes ran (`$9219`, `$7A80`, `$BEEF`): the
|
|
749
|
+
recipe walks the full cycle from its live seed and counts (rung 1,
|
|
750
|
+
VICE x64sc 3.10). The full-cycle argument below covers the rest.
|
|
751
|
+
- 16-bit: taps `$B400` (x^16 + x^14 + x^13 + x^11 + 1). Period measured
|
|
752
|
+
at 65535 the same way (rung 1).
|
|
753
|
+
|
|
754
|
+
```
|
|
755
|
+
step8: lda s8 step16: lsr s16+1
|
|
756
|
+
lsr ror s16
|
|
757
|
+
bcc + bcc +
|
|
758
|
+
eor #$b8 lda s16+1
|
|
759
|
+
+ sta s8 eor #$b4
|
|
760
|
+
sta s16+1
|
|
761
|
+
+
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
Seeding from SID voice 3. Write `$FFFF` to voice 3's frequency
|
|
765
|
+
(`$D40E/$D40F`), gate the noise waveform (`$D412 = $81`), and set bit 7
|
|
766
|
+
of `$D418` (3OFF) so voice 3 is silent while the volume nibble a music
|
|
767
|
+
player owns is left alone; a game ORs the bit into its own `$D418`
|
|
768
|
+
shadow rather than storing a fresh value. Then read `$D41B` twice, a
|
|
769
|
+
few thousand cycles apart (the recipe calls an empty function 100 times
|
|
770
|
+
between the reads, about 3,700 cycles by its own timing figure of 9,476
|
|
771
|
+
cycles for 256 calls), for a 16-bit seed. In the recipe 255 of 255
|
|
772
|
+
consecutive `$D41B` read pairs differed (rung 1), the reads a C loop
|
|
773
|
+
iteration apart, so the register is moving at every read. In a headless
|
|
774
|
+
VICE run the two reads land on the same values every time (the seed was
|
|
775
|
+
`$9219` on PAL in every run, `$7A80` on NTSC), because the emulator's
|
|
776
|
+
noise register starts from a fixed state and the program runs the same
|
|
777
|
+
number of cycles to the read; that the seed varies on a real machine,
|
|
778
|
+
where power-on state and load timing differ, is not measured here
|
|
779
|
+
(rung 4). The seed is the noise register's state, not a clock, so it is
|
|
780
|
+
only as unpredictable as the time between power-on and the read.
|
|
781
|
+
On a SID replacement that cannot read back `$D41B`, such as the
|
|
782
|
+
SwinSID, the seed can be constant or low-entropy (an inference from C64-Wiki
|
|
783
|
+
and SIDDetector-II, not measured here); mix in the CIA timer read at a
|
|
784
|
+
moment the player chose, or the player-input
|
|
785
|
+
count below, which do not depend on the SID. See the pitfall
|
|
786
|
+
`sid_replacement_d41b_unreadable` in `pitfalls/sid.md`.
|
|
787
|
+
|
|
788
|
+
Seeding from a CIA timer. `$DC04/$DC05` is CIA1 timer A, which the
|
|
789
|
+
KERNAL leaves free-running for its jiffy interrupt. Read it as a 16-bit
|
|
790
|
+
value and XOR it into the seed. Read at a fixed point after boot it is
|
|
791
|
+
as reproducible as the SID read (the recipe prints `$251C` on PAL every
|
|
792
|
+
run); read at a moment the player chose it is a good seed.
|
|
793
|
+
|
|
794
|
+
Seeding from player input. Step the LFSR once per frame, or count
|
|
795
|
+
frames, while the title screen waits for the first fire press
|
|
796
|
+
(`joystick_edge_detect` in `techniques/input.md` gives the press). The
|
|
797
|
+
count of frames the player took is the seed, and the timer read at that
|
|
798
|
+
moment adds sixteen bits of sub-frame phase. The recipe cannot show this
|
|
799
|
+
because a headless run has no player; the text here is the design, not
|
|
800
|
+
a measurement.
|
|
801
|
+
|
|
802
|
+
The all-zero state. A Galois LFSR maps state 0 to state 0: nothing falls
|
|
803
|
+
out, nothing is XORed in, and every output is zero for ever. Check the
|
|
804
|
+
seed and replace zero with a constant before the first step (the recipe
|
|
805
|
+
uses `$ACE1`, and `$01` for the 8-bit register when its byte is zero).
|
|
806
|
+
Two `$D41B` reads are both `$00` rarely, but the check costs four
|
|
807
|
+
instructions and the failure is a game that never varies again. See the
|
|
808
|
+
pitfall `lfsr_zero_state_lockup` in `pitfalls/cpu.md`.
|
|
809
|
+
|
|
810
|
+
### Why it works
|
|
811
|
+
|
|
812
|
+
The tap mask is a primitive polynomial over GF(2), so the shift-and-XOR
|
|
813
|
+
map is a permutation of the 2^n - 1 non-zero states in one cycle. That
|
|
814
|
+
is why the period is 255 and 65535 exactly and why a byte histogram over
|
|
815
|
+
one period is flat. The recipe counts the low byte of every 16-bit state
|
|
816
|
+
over the full period: each value 256 times, except `$00` 255 times,
|
|
817
|
+
because state `$0000` is the one that is never visited (rung 1; the
|
|
818
|
+
same figures come from the Python model). For the 8-bit register the
|
|
819
|
+
same argument gives every non-zero byte once and zero never.
|
|
820
|
+
|
|
821
|
+
A flat histogram is not independence. Successive states of a
|
|
822
|
+
right-shifting register are the previous state shifted right with the
|
|
823
|
+
taps folded into a few high bits, so the low byte of state n+1 is the
|
|
824
|
+
low byte of state n shifted right with one new bit at the top. The
|
|
825
|
+
recipe's first bytes show it: `86 43 A1 D0 E8 74 3A` is a right shift
|
|
826
|
+
each step. For a byte a player could not guess by eye, take the high
|
|
827
|
+
byte XOR the low byte, or step the register eight times per byte at
|
|
828
|
+
eight times the cost. Noise pixels and spawn tables do not care; the
|
|
829
|
+
recipe's mosaic paints the low nibble directly and every one of the 16
|
|
830
|
+
colours lands between 43 and 53 times over 760 cells (measured off the
|
|
831
|
+
PAL screenshot).
|
|
832
|
+
|
|
833
|
+
The SID noise source is itself a 23-bit LFSR clocked by voice 3's
|
|
834
|
+
oscillator; `hardware/sid-reference.md` (`$D41B`) has its readback
|
|
835
|
+
rules, including that a held TEST bit drifts it to all ones and never to
|
|
836
|
+
zero. `hardware/c64-registers-reference.md` lists the voice 3 and CIA
|
|
837
|
+
registers.
|
|
838
|
+
|
|
839
|
+
### Variations
|
|
840
|
+
|
|
841
|
+
- 8-bit only: the whole state is the byte, 13 to 14 cycles a step, 255
|
|
842
|
+
bytes before repeating; enough for a starfield, not for a level
|
|
843
|
+
generator.
|
|
844
|
+
- Fibonacci form (XOR several state bits into the new bit) gives the same
|
|
845
|
+
sequences at a higher cost; on the 6510 the Galois form is the one to
|
|
846
|
+
use because the feedback is a single `eor`.
|
|
847
|
+
- Step several times per read, or XOR two registers of coprime period,
|
|
848
|
+
when the shift structure of consecutive bytes would show.
|
|
849
|
+
|
|
850
|
+
### Cycle budget
|
|
851
|
+
|
|
852
|
+
Measured with CIA1 timer A in the recipe: force-load `$FFFF`, 256 calls
|
|
853
|
+
through a function pointer, read the count, with DEN clear so no badline
|
|
854
|
+
interrupts the count (rung 1). Empty loop 9,476 cycles; 8-bit step
|
|
855
|
+
12,933; 16-bit step 14,441. Per step: 13.5 cycles for the 8-bit register
|
|
856
|
+
(13 without the tap, 14 with, absolute addressing) and 19.4 for the
|
|
857
|
+
16-bit one (15 without, 24 with; half the steps take the tap). In zero
|
|
858
|
+
page the same sequences cost 11 to 12 and 13 to 20 (rung 3, from the
|
|
859
|
+
instruction table, not measured).
|
|
860
|
+
|
|
861
|
+
### Recipes
|
|
862
|
+
|
|
863
|
+
- `recipes/oscar64/lfsr-random.md` — seed from `$D41B`, mosaic, period,
|
|
864
|
+
checksum and histogram self-check, timing.
|
|
865
|
+
- `recipes/oscar64/lfsr-random-seed2.md` — the same listing with a
|
|
866
|
+
fixed seed, to show a different seed gives a different picture.
|
|
867
|
+
|
|
868
|
+
The checksum fold in those pages is `chk = ((chk ^ value) * 5 + 1) &
|
|
869
|
+
0xFFFF`, folded from state 1 round the whole cycle so the expected
|
|
870
|
+
value does not depend on the live seed; it is not the rotate fold used
|
|
871
|
+
by `fpcheck.c` above.
|
|
872
|
+
|
|
873
|
+
## compare_16bit_and_signed — 16-bit, signed and ranged compares
|
|
874
|
+
|
|
875
|
+
**Complexity:** low
|
|
876
|
+
**Region:** both
|
|
877
|
+
**Uses registers:** (none)
|
|
878
|
+
**Cost:** cycles_per_frame=20
|
|
879
|
+
**Cost basis:** measured-vice
|
|
880
|
+
**Cost measured on:** kickassembler-compare-16bit-signed (one call, signed 16-bit with overflow)
|
|
881
|
+
|
|
882
|
+
### Why
|
|
883
|
+
|
|
884
|
+
`CMP` compares one unsigned byte. A tile collision compares a 16-bit
|
|
885
|
+
world coordinate against a boundary, a fixed-point mover compares a
|
|
886
|
+
signed velocity against a limit, and a bounds check asks whether a
|
|
887
|
+
value lies inside a range. Each needs a sequence of instructions and a
|
|
888
|
+
particular flag read afterwards, and the obvious sequences are wrong
|
|
889
|
+
at the edges: a bare `BMI` after a subtract gives the wrong answer for
|
|
890
|
+
one pair in four. Every flag and cycle figure below was measured in
|
|
891
|
+
VICE x64sc 3.10 by `recipes/kickassembler/compare-16bit-signed.md`,
|
|
892
|
+
which prints the flags on the boundary pairs and checks every idiom
|
|
893
|
+
over full sweeps against a Python model.
|
|
894
|
+
|
|
895
|
+
### How
|
|
896
|
+
|
|
897
|
+
**Unsigned 16-bit.** Compare the high bytes; if they differ they decide,
|
|
898
|
+
otherwise compare the low bytes:
|
|
899
|
+
|
|
900
|
+
```asm
|
|
901
|
+
lda a_hi
|
|
902
|
+
cmp b_hi
|
|
903
|
+
bne done // high bytes differ: their flags are the answer
|
|
904
|
+
lda a_lo
|
|
905
|
+
cmp b_lo // high bytes equal: the low bytes decide
|
|
906
|
+
done: // C clear: a < b. C set: a >= b. Z set: a == b
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
After the first `CMP`, `C` set means `a_hi >= b_hi` and `Z` set means
|
|
910
|
+
the high bytes are equal. `BNE` skips the low bytes when they are not.
|
|
911
|
+
At `done`, `Z` can only be set by the low-byte `CMP` after the high
|
|
912
|
+
bytes matched, so it means the whole words are equal. `$0100` against
|
|
913
|
+
`$00FF` ends with `C` set and `Z` clear from the first `CMP` alone;
|
|
914
|
+
`$00FF` against `$0100` ends with `N` set and `C` clear. For "`a > b`"
|
|
915
|
+
test `C` set and `Z` clear; for "`a <= b`" swap the operands.
|
|
916
|
+
|
|
917
|
+
**Signed 8-bit.** Subtract, then correct the sign when the subtract
|
|
918
|
+
overflowed:
|
|
919
|
+
|
|
920
|
+
```asm
|
|
921
|
+
lda a
|
|
922
|
+
sec
|
|
923
|
+
sbc b
|
|
924
|
+
bvc ok // V clear: N is the sign of a - b
|
|
925
|
+
eor #$80 // V set: the sign wrapped, flip it back
|
|
926
|
+
ok: // N set: a < b (signed). N clear: a >= b
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
Measured flags on the boundary pairs (`N V Z C`, `.` for clear):
|
|
930
|
+
|
|
931
|
+
| a | b | after `SBC` | after fix-up | idiom says | bare `BMI` says | truth |
|
|
932
|
+
|---|---|---|---|---|---|---|
|
|
933
|
+
| `$80` (-128) | `$7F` (127) | `. V . C` | `N V . C` | less | not less | less |
|
|
934
|
+
| `$7F` (127) | `$80` (-128) | `N V . .` | `. V . .` | not less | less | not less |
|
|
935
|
+
| `$00` | `$00` | `. . Z C` | `. . Z C` | not less | not less | not less |
|
|
936
|
+
| `$FF` (-1) | `$00` | `N . . C` | `N . . C` | less | less | less |
|
|
937
|
+
| `$00` | `$FF` (-1) | `. . . .` | `. . . .` | not less | not less | not less |
|
|
938
|
+
| `$80` | `$80` | `. . Z C` | `. . Z C` | not less | not less | not less |
|
|
939
|
+
| `$7F` | `$7F` | `. . Z C` | `. . Z C` | not less | not less | not less |
|
|
940
|
+
| `$9C` (-100) | `$64` (100) | `. V . C` | `N V . C` | less | not less | less |
|
|
941
|
+
|
|
942
|
+
`-128 - 127` is `-255`, which does not fit in a byte; the byte result is
|
|
943
|
+
`$01`, `N` clear, and `V` set says so. `EOR #$80` makes it `$81` and
|
|
944
|
+
`EOR` sets `N` from its result, so `N` is the true sign. The bare `BMI`
|
|
945
|
+
is wrong on exactly the rows where `V` is set. Over all 65,536 pairs it
|
|
946
|
+
disagrees with the corrected form on 16,384, one quarter (measured;
|
|
947
|
+
the Python model gives the same count). A value that never crosses
|
|
948
|
+
the overflow boundary hides the fault: two positions within 127 of
|
|
949
|
+
each other compare correctly either way, and the bug appears the first
|
|
950
|
+
time a velocity or an offset is large and of the other sign.
|
|
951
|
+
|
|
952
|
+
**Signed 16-bit.** Set the borrow from the low bytes with `CMP`, then
|
|
953
|
+
subtract the high bytes and apply the same fix-up:
|
|
954
|
+
|
|
955
|
+
```asm
|
|
956
|
+
lda a_lo
|
|
957
|
+
cmp b_lo // C = no borrow from the low bytes
|
|
958
|
+
lda a_hi
|
|
959
|
+
sbc b_hi
|
|
960
|
+
bvc ok
|
|
961
|
+
eor #$80
|
|
962
|
+
ok: // N set: a < b (signed)
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
Measured on the boundary pairs: `$7FFF` against `$8000` ends `. V . .`
|
|
966
|
+
with `N` clear (32,767 is not less than -32,768) where the unsigned
|
|
967
|
+
form ends `N . . .` with `C` clear (as unsigned, `$7FFF` is less);
|
|
968
|
+
`$8000` against `$7FFF` ends `N V . C`, less, where the unsigned form
|
|
969
|
+
says not less; equal values end `. . Z C` in both forms; `$0000`
|
|
970
|
+
against `$FFFF` ends `. . Z .`, not less, and shows that `Z` at the end
|
|
971
|
+
of the signed form is the high byte's alone, not equality. Test equality
|
|
972
|
+
with the unsigned form.
|
|
973
|
+
|
|
974
|
+
**Ranged, unsigned 8-bit.** Is `x` in `[lo, lo + w)`:
|
|
975
|
+
|
|
976
|
+
```asm
|
|
977
|
+
lda x
|
|
978
|
+
sec
|
|
979
|
+
sbc lo
|
|
980
|
+
cmp w // C clear: inside. C set: outside
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
`x - lo` modulo 256 is below `w` exactly when `lo <= x < lo + w`, as
|
|
984
|
+
long as `lo + w` does not exceed 256: an `x` below `lo` wraps to at
|
|
985
|
+
least `256 - lo`, which is at least `w`. One `BCC inside` acts on it.
|
|
986
|
+
Measured over every `x` against every `lo` with `w = 32` (clamped near
|
|
987
|
+
the top): matches the model. The two-compare form, `cmp lo / bcc out /
|
|
988
|
+
cmp hi / bcs out`, needs no width and no wrap condition, and is the
|
|
989
|
+
one to use when `hi` is what you hold.
|
|
990
|
+
|
|
991
|
+
### Why it works
|
|
992
|
+
|
|
993
|
+
`CMP` is a subtract that discards the result and does not touch `V`
|
|
994
|
+
(measured: the flag column after `CMP` alone shows the `V` the previous
|
|
995
|
+
instruction left, which is why the recipe clears it first). It sets `C`
|
|
996
|
+
when no borrow was needed, that is when `A >= operand` unsigned, and `Z`
|
|
997
|
+
when the two are equal; `N` after `CMP` is bit 7 of a discarded
|
|
998
|
+
difference and means nothing about order. `SBC` keeps the result and
|
|
999
|
+
does set `V`: the two's complement sign of `a - b` is the order only
|
|
1000
|
+
when the difference fits in seven bits plus sign, and `V` is the
|
|
1001
|
+
report that it did not. Flipping bit 7 when `V` is set restores the
|
|
1002
|
+
sign because overflow in either direction flips it exactly once.
|
|
1003
|
+
|
|
1004
|
+
### The C compiler's view
|
|
1005
|
+
|
|
1006
|
+
Oscar64 (`-O2`, read from its `.asm` listing; the four functions were
|
|
1007
|
+
marked `__noinline` so they stayed separate, since `-O2` had inlined
|
|
1008
|
+
them into `main` and dropped three whose result was overwritten)
|
|
1009
|
+
emits the same high-byte-first `CMP / BNE / CMP` chain for `unsigned
|
|
1010
|
+
int` and turns the carry into a value with `LDA #0 / ROL / EOR #1`. For
|
|
1011
|
+
`int` it uses the chain too, but when the high bytes differ it does not
|
|
1012
|
+
use `SBC` and `V`: it exclusive-ors the two high bytes and tests the
|
|
1013
|
+
sign of that. If the signs agree (`BPL`) the unsigned carry is the
|
|
1014
|
+
answer; if they differ the negative one is the smaller, and the carry
|
|
1015
|
+
from the unsigned compare already says which is negative. For `signed
|
|
1016
|
+
char` it emits `CMP / BEQ / EOR b / BCC / BMI`, the same sign-agreement
|
|
1017
|
+
test. For `x >= lo && x < hi` on `unsigned char` it emits `CMP lo / BCS
|
|
1018
|
+
/ CMP hi` with the `LDA #0 / ROL / EOR #1` tail. A C `int` compare is
|
|
1019
|
+
correct at the boundary; what costs is the value it materialises when
|
|
1020
|
+
all you wanted was a branch, and `-O2` folds that away when the result
|
|
1021
|
+
feeds an `if`.
|
|
1022
|
+
|
|
1023
|
+
### Variations
|
|
1024
|
+
|
|
1025
|
+
- **Three-byte and 16.8 compares** extend the unsigned chain one byte
|
|
1026
|
+
from the top, or carry the `CMP` borrow through two `SBC`s for the
|
|
1027
|
+
signed form (rung 3, not measured here).
|
|
1028
|
+
- **Signed against a constant** needs no `SBC`: `lda v / cmp #lim` is
|
|
1029
|
+
correct when both are in the same half, and `eor #$80` on both sides
|
|
1030
|
+
(`lda v / eor #$80 / cmp #(lim ^ $80)`) turns any signed compare
|
|
1031
|
+
into an unsigned one, the trick the Oscar64 `signed char` code is
|
|
1032
|
+
a variant of.
|
|
1033
|
+
- **Equality only** is `lda a_lo / cmp b_lo / bne no / lda a_hi / cmp
|
|
1034
|
+
b_hi / bne no`; the order of bytes does not matter for it.
|
|
1035
|
+
|
|
1036
|
+
### Cycle budget
|
|
1037
|
+
|
|
1038
|
+
One call, operands absolute, no result branch, net of an empty
|
|
1039
|
+
`JSR / RTS`, identical on PAL and NTSC:
|
|
1040
|
+
|
|
1041
|
+
| Idiom | Cycles |
|
|
1042
|
+
|---|---|
|
|
1043
|
+
| unsigned 16-bit, high bytes differ | 11 |
|
|
1044
|
+
| unsigned 16-bit, high bytes equal | 18 |
|
|
1045
|
+
| signed 8-bit | 13 (no overflow) / 14 (overflow) |
|
|
1046
|
+
| signed 16-bit | 19 (no overflow) / 20 (overflow) |
|
|
1047
|
+
| range test | 14 |
|
|
1048
|
+
|
|
1049
|
+
Each equals its instruction-table sum. The two signed rows depend on
|
|
1050
|
+
the path: when the subtract does not overflow the `BVC` is taken for 3
|
|
1051
|
+
and the `EOR` is skipped; when it does, the `BVC` falls through for 2
|
|
1052
|
+
and the `EOR #$80` costs 2 more. The recipe times the overflow pairs,
|
|
1053
|
+
so its screen shows 14 and 20. Add the branch that acts on the
|
|
1054
|
+
result: 2 not taken, 3 taken, 4 taken across a page (`pitfalls/cpu.md`,
|
|
1055
|
+
`branch_page_cross_extra_cycle`). Zero-page operands save one cycle per
|
|
1056
|
+
access (rung 3). The `**Cost:**` line carries the signed 16-bit figure,
|
|
1057
|
+
the worst of the five, as one call per frame.
|
|
1058
|
+
|
|
1059
|
+
### Recipes
|
|
1060
|
+
|
|
1061
|
+
- `recipes/kickassembler/compare-16bit-signed.md` — every idiom over
|
|
1062
|
+
the boundary pairs with flags on screen, full sweeps against a
|
|
1063
|
+
Python checksum, the bare `BMI` miss count, and the timings.
|
|
1064
|
+
|
|
1065
|
+
## isqrt_16bit — Integer square root of a 16-bit value
|
|
1066
|
+
|
|
1067
|
+
**Complexity:** low
|
|
1068
|
+
**Region:** both
|
|
1069
|
+
**Uses registers:** (none)
|
|
1070
|
+
**Cost:** cycles_per_frame=869
|
|
1071
|
+
**Cost basis:** measured-vice
|
|
1072
|
+
**Cost measured on:** kickassembler-sqrt-atan2 (one call, worst input)
|
|
1073
|
+
|
|
1074
|
+
### Why
|
|
1075
|
+
|
|
1076
|
+
A distance. A homing missile, a proximity trigger, a guard that
|
|
1077
|
+
notices the player inside a radius, a spring force that weakens with
|
|
1078
|
+
range: each wants `sqrt(dx*dx + dy*dy)`, and the 6510 has no square
|
|
1079
|
+
root. Often the game does not need the root itself, only a comparison
|
|
1080
|
+
against a radius, and the variation below does that without one. When
|
|
1081
|
+
the distance is used as a number, to scale a speed or index a volume
|
|
1082
|
+
table, this routine gives the floor of the root of any 16-bit value
|
|
1083
|
+
in under 900 cycles with no table. The figures on this technique were
|
|
1084
|
+
measured with CIA1 timer A, not the timer B the page's introduction
|
|
1085
|
+
names, in `recipes/kickassembler/sqrt-atan2.md` (rung 1, VICE x64sc
|
|
1086
|
+
3.10, PAL and NTSC identical).
|
|
1087
|
+
|
|
1088
|
+
### How
|
|
1089
|
+
|
|
1090
|
+
Restoring shift-and-subtract, the same shape as `division_8_16bit`
|
|
1091
|
+
above but taking two bits of the input per pass and testing a trial
|
|
1092
|
+
value that grows with the answer. Keep a 16-bit remainder and an
|
|
1093
|
+
8-bit root, both zero. Eight passes: shift the top two bits of the
|
|
1094
|
+
input into the bottom of the remainder; form the trial
|
|
1095
|
+
`4 * root + 1`; double the root; if the remainder is at least the
|
|
1096
|
+
trial, subtract it and set the root's new bottom bit. After eight
|
|
1097
|
+
passes the root is `floor(sqrt(n))` and the remainder is
|
|
1098
|
+
`n - root * root`. The trial reaches 509 and the remainder 1,019,
|
|
1099
|
+
so both need two bytes and the compare is a 16-bit one, high bytes
|
|
1100
|
+
first, low bytes only when they tie; the branch into the subtract
|
|
1101
|
+
arrives with the carry set on both paths, so the `SBC` is exact with
|
|
1102
|
+
no `SEC`. The recipe's listing is the reference form; it was checked
|
|
1103
|
+
for the exact root on 35 cases against `math.isqrt` and for
|
|
1104
|
+
`rem <= 2 * root`, which is `n < (root + 1)^2`, on all 65,536 inputs
|
|
1105
|
+
on the machine.
|
|
1106
|
+
|
|
1107
|
+
A table-assisted form was considered and not used. A 256-entry table
|
|
1108
|
+
gives the root of an 8-bit value directly, but a 16-bit input needs
|
|
1109
|
+
either a 64 KB table or a two-stage estimate and a correction step
|
|
1110
|
+
with a multiply, and the loop is simpler, needs no memory and is fast
|
|
1111
|
+
enough for a routine called a few times a frame.
|
|
1112
|
+
|
|
1113
|
+
### Why it works
|
|
1114
|
+
|
|
1115
|
+
Each pass decides one bit of the root from the top, keeping the
|
|
1116
|
+
invariant `n_seen = root^2 + rem` over the bits consumed so far. If
|
|
1117
|
+
the next root bit is 1 the root becomes `2 * root + 1` and its square
|
|
1118
|
+
grows by `(2 * root + 1)^2 - (2 * root)^2 = 4 * root + 1`, which is
|
|
1119
|
+
the trial; the bit is 1 exactly when the remainder, now holding two
|
|
1120
|
+
more bits of `n`, can absorb that. It is long division with a
|
|
1121
|
+
divisor that is rebuilt from the quotient so far.
|
|
1122
|
+
|
|
1123
|
+
### Variations
|
|
1124
|
+
|
|
1125
|
+
- **Compare squares, no root.** For "is the target within radius r"
|
|
1126
|
+
compute `dx*dx + dy*dy` with `table_multiply_8x8` (two multiplies,
|
|
1127
|
+
52 cycles each with the tables in place) and compare the 16-bit sum
|
|
1128
|
+
against `r*r` held as a constant. About 130 cycles by the
|
|
1129
|
+
instruction table (rung 3, not measured here), and exact. This is
|
|
1130
|
+
the common case in a game; take the root only when the distance is
|
|
1131
|
+
used as a number.
|
|
1132
|
+
- **Octagonal estimate.** `max + min / 2` over `|dx|` and `|dy|` is
|
|
1133
|
+
within about 12 per cent of the true distance and costs a compare
|
|
1134
|
+
and a shift (rung 4, not measured here). Good enough to rank targets
|
|
1135
|
+
by nearness.
|
|
1136
|
+
- **8-bit input.** For `n` below 256 a 256-byte table of roots is one
|
|
1137
|
+
indexed load; the loop above with four passes over an 8-bit input
|
|
1138
|
+
is the same routine at half the cost (rung 3, not measured here).
|
|
1139
|
+
|
|
1140
|
+
### Cycle budget
|
|
1141
|
+
|
|
1142
|
+
Body only, operands in zero page, net of `JSR` and `RTS`, measured
|
|
1143
|
+
over every input: 869 cycles at worst, first reached at 65025, which
|
|
1144
|
+
is 255 squared; 65535 costs 862. The instruction-table model of the
|
|
1145
|
+
loop gives the same two figures and 869 as its maximum, so the loop is
|
|
1146
|
+
about 105 cycles a pass plus 13 of setup. The Cost line carries the
|
|
1147
|
+
worst case; one call is 4.4 per cent of a PAL frame. Keep the loop in
|
|
1148
|
+
one page: a layout with its branch across a page boundary measured
|
|
1149
|
+
876, seven taken branches at one cycle each.
|
|
1150
|
+
|
|
1151
|
+
### Recipes
|
|
1152
|
+
|
|
1153
|
+
- `recipes/kickassembler/sqrt-atan2.md` — the root and the atan2
|
|
1154
|
+
below, 35 root cases against a Python model, the remainder
|
|
1155
|
+
invariant on every input, and the worst-case timing sweep on
|
|
1156
|
+
screen.
|
|
1157
|
+
|
|
1158
|
+
## atan2_8bit — Aim angle from a signed offset, 256 units a turn
|
|
1159
|
+
|
|
1160
|
+
**Complexity:** medium
|
|
1161
|
+
**Region:** both
|
|
1162
|
+
**Uses registers:** (none)
|
|
1163
|
+
**Requires:** division_8_16bit
|
|
1164
|
+
**Cost:** cycles_per_frame=381
|
|
1165
|
+
**Cost basis:** measured-vice
|
|
1166
|
+
**Cost measured on:** kickassembler-sqrt-atan2 (one call, worst pair)
|
|
1167
|
+
|
|
1168
|
+
### Why
|
|
1169
|
+
|
|
1170
|
+
A turret that turns toward the player, an enemy that fires along the
|
|
1171
|
+
line to the ship, a homing shot: each needs the angle of the offset
|
|
1172
|
+
`(dx, dy)` from itself to the target, in the same units as its sine
|
|
1173
|
+
table so the angle can be turned straight back into a velocity. A
|
|
1174
|
+
byte angle, 256 units a turn, is that unit on this machine. The
|
|
1175
|
+
routine here takes signed 8-bit `dx` and `dy` and returns the angle
|
|
1176
|
+
to within one unit, measured over every one of the 65,536 pairs by
|
|
1177
|
+
the host model and confirmed on the machine over 36 cases in
|
|
1178
|
+
`recipes/kickassembler/sqrt-atan2.md` (rung 1, VICE x64sc 3.10). The
|
|
1179
|
+
convention is 0 for `+dx`, 64 for `+dy`, so with screen y growing
|
|
1180
|
+
downward the angle runs clockwise as seen on the screen.
|
|
1181
|
+
|
|
1182
|
+
### How
|
|
1183
|
+
|
|
1184
|
+
Three steps. Fold: take `|dx|` and `|dy|` by testing bit 7 of each
|
|
1185
|
+
input with `BPL` and negating, remember the two signs, and compare
|
|
1186
|
+
the magnitudes; the smaller over the larger is a ratio between 0 and
|
|
1187
|
+
1, which is the first octant. Look up: the ratio index is
|
|
1188
|
+
`floor(min * 256 / max)`, an 8-bit quotient from an eight-pass
|
|
1189
|
+
shift-and-subtract divide, the loop of `division_8_16bit` with the
|
|
1190
|
+
dividend's high byte equal to `min` and its low byte zero; because
|
|
1191
|
+
`max` is at most 128 the remainder never needs the ninth-bit guard
|
|
1192
|
+
the general loop carries. The index reads a 256-byte table whose
|
|
1193
|
+
entry `i` is `round(atan(i / 256) * 256 / 2pi)`, 0 to 32; the recipe
|
|
1194
|
+
quotes the Python that prints it. Unfold: if `|dy|` was the larger
|
|
1195
|
+
the angle is `64 - a`; if `dx` was negative, `128 - a`; if `dy` was
|
|
1196
|
+
negative, `256 - a`. Equal magnitudes are exactly 32 and are tested
|
|
1197
|
+
for, since the ratio 256 does not fit the index; `(0, 0)` returns 0.
|
|
1198
|
+
|
|
1199
|
+
The inputs must be true 8-bit differences. A game that forms `dx` as
|
|
1200
|
+
`target_x - x` with `SEC / SBC` over positions more than 127 apart
|
|
1201
|
+
gets a wrapped byte whose sign is wrong, and the fold then answers
|
|
1202
|
+
for the opposite half of the circle. Clamp the difference, or take
|
|
1203
|
+
it in 16 bits and use the high byte's sign with a clamped low byte
|
|
1204
|
+
(`pitfalls/cpu.md`, `signed_compare_bmi_overflow`).
|
|
1205
|
+
|
|
1206
|
+
### Why it works
|
|
1207
|
+
|
|
1208
|
+
`atan2` over the whole plane is eight copies of `atan` over one
|
|
1209
|
+
octant, related by reflection in the axes and the diagonal, and each
|
|
1210
|
+
reflection is a subtraction from a constant: `64 - a` across the
|
|
1211
|
+
diagonal, `128 - a` across the vertical axis, `-a` across the
|
|
1212
|
+
horizontal. The octant needs only `atan` of a ratio in `[0, 1)`,
|
|
1213
|
+
which is a 256-entry table when the ratio is quantised to 1/256. The
|
|
1214
|
+
angle changes by less than 0.2 units across one ratio step, so the
|
|
1215
|
+
truncated index costs less than that and the rounding of the table
|
|
1216
|
+
entry costs at most half a unit; over every pair the largest error is
|
|
1217
|
+
1 unit, on 3,968 of the 65,536 pairs. Those pairs are spread across
|
|
1218
|
+
the whole octant, not gathered at the axes: counted by tenths of the
|
|
1219
|
+
ratio `min / max`, the first three tenths from the axis hold 1,680 of
|
|
1220
|
+
them and the last three before the diagonal 688 (the generator's
|
|
1221
|
+
model, rung 3).
|
|
1222
|
+
|
|
1223
|
+
### Variations
|
|
1224
|
+
|
|
1225
|
+
- **Sixteen directions, no table, no divide.** The sign bits and the
|
|
1226
|
+
magnitude compare give the octant; one more compare, `min * 2 < max`
|
|
1227
|
+
(a shift and a `CMP`), splits each octant at 26.6 degrees, which is
|
|
1228
|
+
`atan(1/2)`, so the two sectors are 26.6 and 18.4 degrees wide, not
|
|
1229
|
+
equal. An equal split at 22.5 degrees needs `min / max < 0.414`,
|
|
1230
|
+
which `min * 2 + min / 2 < max` gives closely: two shifts, an add and
|
|
1231
|
+
the `CMP`, a few cycles more. Sixteen sectors from three compares,
|
|
1232
|
+
about 40 cycles for the unequal form by the instruction table
|
|
1233
|
+
(rung 3, not measured here), enough for a sprite with sixteen facing
|
|
1234
|
+
frames or a shot that picks one of sixteen velocity pairs.
|
|
1235
|
+
- **Smaller table.** Index with `ratio >> 2` for a 64-byte table or
|
|
1236
|
+
`ratio >> 3` for 32 bytes; the error grows with the step and was
|
|
1237
|
+
not measured here.
|
|
1238
|
+
- **Angle to velocity.** Feed the result to a sine table with 256
|
|
1239
|
+
entries a turn (`sine_table_generation`, `techniques/cpu-cycle-tricks.md`)
|
|
1240
|
+
for `dy` and the same table 64 entries on for `dx`; the angle unit
|
|
1241
|
+
was chosen so no scaling sits between the two.
|
|
1242
|
+
|
|
1243
|
+
### Cycle budget
|
|
1244
|
+
|
|
1245
|
+
Body only, zero-page operands, net of `JSR` and `RTS`, measured over
|
|
1246
|
+
every pair: 381 cycles at worst, first reached at `dx = -127`,
|
|
1247
|
+
`dy = -128`, a pair that negates both inputs, takes the second-octant
|
|
1248
|
+
path and subtracts on most divide passes. The divide is about 290 of
|
|
1249
|
+
that; the fold and unfold about 90 (rung 3, split from the
|
|
1250
|
+
instruction table; the total is the measured figure). The Cost line
|
|
1251
|
+
carries the worst case, 1.9 per cent of a PAL frame per call. The
|
|
1252
|
+
same page-crossing caution as the root applies: the recipe holds both
|
|
1253
|
+
routines in one page with an `.assert`.
|
|
1254
|
+
|
|
1255
|
+
### Recipes
|
|
1256
|
+
|
|
1257
|
+
- `recipes/kickassembler/sqrt-atan2.md` — the fold, divide and table
|
|
1258
|
+
as listed, 36 angle cases over the axes and every octant against a
|
|
1259
|
+
Python model, and the worst-case timing sweep over all 65,536 pairs.
|
|
1260
|
+
|
|
1261
|
+
## procedural_seed_universe — A reproducible galaxy from three 16-bit seeds
|
|
1262
|
+
|
|
1263
|
+
**Complexity:** low
|
|
1264
|
+
**Region:** both
|
|
1265
|
+
**Uses registers:** (none)
|
|
1266
|
+
**Cost:** cycles_per_frame=733
|
|
1267
|
+
**Cost basis:** measured-vice
|
|
1268
|
+
**Cost measured on:** kickassembler-wireframe-ships (one system per call, display on)
|
|
1269
|
+
**Claims:** none
|
|
1270
|
+
**Claims basis:** derived-listing
|
|
1271
|
+
|
|
1272
|
+
### Why
|
|
1273
|
+
|
|
1274
|
+
A trading or exploration game wants hundreds of named places, and the
|
|
1275
|
+
machine has no room to store them. Elite's answer is to store six bytes
|
|
1276
|
+
and regenerate every system from them on demand. The same seeds always
|
|
1277
|
+
give the same galaxy, so nothing but the seeds and the player's
|
|
1278
|
+
position needs saving.
|
|
1279
|
+
|
|
1280
|
+
### How
|
|
1281
|
+
|
|
1282
|
+
Keep three 16-bit seeds `s0`, `s1`, `s2`. One twist is a
|
|
1283
|
+
Tribonacci-style step, 16-bit with carries out of bit 15 dropped:
|
|
1284
|
+
|
|
1285
|
+
```text
|
|
1286
|
+
s0' = s1
|
|
1287
|
+
s1' = s2
|
|
1288
|
+
s2' = s0 + s1 + s2
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
A system's data comes from bits of its seeds, and four twists move the
|
|
1292
|
+
seeds on to the next system. Elite's rules for galaxy 1 (Mark Moxon's
|
|
1293
|
+
deep dives):
|
|
1294
|
+
|
|
1295
|
+
- Start seeds `s0 = $5A4A`, `s1 = $0248`, `s2 = $B753` (Tibedied).
|
|
1296
|
+
- Galactic x is the high byte of `s1`; y is the high byte of `s0`
|
|
1297
|
+
shifted right once, because the chart is half as tall as it is wide.
|
|
1298
|
+
- The name is two-letter pairs. Before each of the first three twists,
|
|
1299
|
+
bits 0-4 of the high byte of `s2` pick a pair from a 32-entry table;
|
|
1300
|
+
0 adds nothing. If bit 6 of the low byte of `s0` is set, the fourth
|
|
1301
|
+
twist adds a fourth pair, for up to 8 letters. One table entry has a
|
|
1302
|
+
silent second letter.
|
|
1303
|
+
- A galactic jump rotates each of the six seed bytes left by one bit, so
|
|
1304
|
+
eight jumps return to galaxy 1: 8 galaxies of 256 systems.
|
|
1305
|
+
|
|
1306
|
+
In a game, keep the current system's seeds, twist forwards to walk the
|
|
1307
|
+
galaxy, and restart from the galaxy's seeds to go back.
|
|
1308
|
+
|
|
1309
|
+
### Why it works
|
|
1310
|
+
|
|
1311
|
+
The twist is a linear recurrence over 16-bit integers, so it is fully
|
|
1312
|
+
determined by its six bytes and cheap on a 6502: two 16-bit adds and
|
|
1313
|
+
two moves. The name routine consumes the same twists
|
|
1314
|
+
that advance to the next system, so one routine yields the name and the
|
|
1315
|
+
next seeds.
|
|
1316
|
+
|
|
1317
|
+
Moxon gives a worked example that checks an implementation: Lave's
|
|
1318
|
+
seeds are `s0 = $AD38`, `s1 = $149C`, `s2 = $151D`, its name pairs are
|
|
1319
|
+
tokens 149 (LA) and 150 (VE), and it is a short name (bit 6 of `$38` is
|
|
1320
|
+
clear). The recipe's generator, original code following those rules,
|
|
1321
|
+
produces LAVE as the eighth system of galaxy 1 with those seeds, at
|
|
1322
|
+
x = 20, y = 86 (`$AD >> 1`). Its first eight names, TIBEDIED, QUBE,
|
|
1323
|
+
LELEER, BIARGE, XEQUERIN, TIRAOR, RABEDIRA, LAVE, match its Python model
|
|
1324
|
+
on PAL and NTSC. The seed and bit rules are Elite's as Moxon documents
|
|
1325
|
+
them. The token string is data from Elite (Ian Bell and David Braben,
|
|
1326
|
+
1984) as Moxon lists it at QQ16. Index 0 is written `..` because it is
|
|
1327
|
+
never used, and the silent letter is written `.`.
|
|
1328
|
+
|
|
1329
|
+
### Variations
|
|
1330
|
+
|
|
1331
|
+
- **Your own world.** Keep the twist and choose your own fields: a
|
|
1332
|
+
planet's economy, tech level or government from other seed bits
|
|
1333
|
+
(Elite does this too; Moxon's "Generating system data").
|
|
1334
|
+
- **More or fewer twists.** Four twists a system is Elite's choice. Any
|
|
1335
|
+
fixed count gives a reproducible sequence; fewer cost less. How the
|
|
1336
|
+
count changes the look of the galaxy was not measured here.
|
|
1337
|
+
- **Contrast with an LFSR.** `lfsr_random` above steps one register and
|
|
1338
|
+
suits noise and dice. The seed twist is the same idea with a 48-bit
|
|
1339
|
+
state and data read straight from the state bits.
|
|
1340
|
+
|
|
1341
|
+
### Cycle budget
|
|
1342
|
+
|
|
1343
|
+
One system (coordinates, name, four twists) measured 733 cycles at
|
|
1344
|
+
worst over the first eight systems, in VICE x64sc on PAL and NTSC with
|
|
1345
|
+
the display on, CIA1-timed around the call. A twist is 76 cycles with
|
|
1346
|
+
its `JSR` and `RTS`, by the instruction table, so four twists are about
|
|
1347
|
+
300 of that. The Cost line is one system per call. Walking all 256
|
|
1348
|
+
systems of a galaxy at that rate is about 188,000 cycles (arithmetic),
|
|
1349
|
+
nearly ten PAL frames: a galaxy chart is drawn over several frames, or
|
|
1350
|
+
its x and y are cached in 512 bytes.
|
|
1351
|
+
|
|
1352
|
+
### Recipes
|
|
1353
|
+
|
|
1354
|
+
- `recipes/kickassembler/wireframe-ships.md`: generates and prints the
|
|
1355
|
+
first eight systems of galaxy 1 and checks names and coordinates
|
|
1356
|
+
against a Python model.
|
|
1357
|
+
|
|
1358
|
+
### Sources
|
|
1359
|
+
|
|
1360
|
+
- Mark Moxon, "Twisting the system seeds":
|
|
1361
|
+
https://elite.bbcelite.com/deep_dives/twisting_the_system_seeds.html
|
|
1362
|
+
- Mark Moxon, "Generating system names":
|
|
1363
|
+
https://elite.bbcelite.com/deep_dives/generating_system_names.html
|
|
1364
|
+
- Mark Moxon, "Generating system data":
|
|
1365
|
+
https://elite.bbcelite.com/deep_dives/generating_system_data.html
|
|
1366
|
+
- Mark Moxon, C64 Elite variable QQ16 (the two-letter tokens):
|
|
1367
|
+
https://elite.bbcelite.com/c64/main/variable/qq16.html
|
|
1368
|
+
|
|
1369
|
+
## basic_rom_float_calls — The BASIC ROM's floating-point routines, called from machine code
|
|
1370
|
+
|
|
1371
|
+
**Complexity:** low
|
|
1372
|
+
**Region:** both
|
|
1373
|
+
**Uses registers:** (none)
|
|
1374
|
+
**Requires:** cpu_io_port_bank
|
|
1375
|
+
**Cost:** cycles_per_frame=1079, zp_bytes=26
|
|
1376
|
+
**Cost basis:** measured-vice
|
|
1377
|
+
**Cost measured on:** kickassembler-basic-float-calls (one FMULT call, display off; the zero-page span $57 to $70 the calls use)
|
|
1378
|
+
|
|
1379
|
+
### Why
|
|
1380
|
+
|
|
1381
|
+
A 40-bit float library for free: five-byte add, subtract, multiply,
|
|
1382
|
+
divide, square root, powers, logs, trig, integer conversion in both
|
|
1383
|
+
directions and a decimal printer, all in the BASIC ROM that a `SYS`
|
|
1384
|
+
leaves banked in. When speed does not matter, a level editor's
|
|
1385
|
+
statistics, a high-score table's percentage, a one-off table built at
|
|
1386
|
+
start-up, a trainer's or a tool's arithmetic, it is not worth writing.
|
|
1387
|
+
It is worth nothing in a frame loop: the multiply alone is a twentieth
|
|
1388
|
+
of a PAL frame and the root is more than two frames (measured, below).
|
|
1389
|
+
The figures on this technique were measured with CIA1 timer A in
|
|
1390
|
+
`recipes/kickassembler/basic-float-calls.md` (rung 1, VICE x64sc 3.10,
|
|
1391
|
+
PAL and NTSC identical), and every address was read from the ROM with
|
|
1392
|
+
the monitor's `d` command before it was used; the dump is on that page.
|
|
1393
|
+
|
|
1394
|
+
These are BASIC ROM routines, not KERNAL ones. They have no jump-table
|
|
1395
|
+
entry and no vector, so they are not KERNAL routine nodes in this
|
|
1396
|
+
knowledge base and the `uses_kernal` line of a recipe does not name
|
|
1397
|
+
them; the recipe lists the KERNAL `CHROUT` it prints with and nothing
|
|
1398
|
+
else. Their addresses are fixed by the BASIC ROM revision (901226-01
|
|
1399
|
+
here) and do not move with a KERNAL swap.
|
|
1400
|
+
|
|
1401
|
+
### How
|
|
1402
|
+
|
|
1403
|
+
Bank the BASIC ROM in: `$01 = $37`, which is the value at `SYS`. The
|
|
1404
|
+
routines, with the first instruction the monitor showed at each and the
|
|
1405
|
+
calling convention:
|
|
1406
|
+
|
|
1407
|
+
| Name | Address | First instruction | Convention |
|
|
1408
|
+
|---|---|---|---|
|
|
1409
|
+
| `GIVAYF` | `$B391` | `LDX #$00` | A = high byte, Y = low byte of a signed 16-bit integer; FAC = that value |
|
|
1410
|
+
| `MOVFM` | `$BBA2` | `STA $22` | A = low, Y = high of a five-byte float; FAC = it |
|
|
1411
|
+
| `MOVMF` | `$BBD4` | `JSR $BC1B` | X = low, Y = high of five bytes; rounds FAC and stores it there |
|
|
1412
|
+
| `CONUPK` | `$BA8C` | `STA $22` | A/Y as `MOVFM`; ARG = it. The four arithmetic entries call it first |
|
|
1413
|
+
| `FADD` | `$B867` | `JSR $BA8C` | FAC = mem(A/Y) + FAC |
|
|
1414
|
+
| `FSUB` | `$B850` | `JSR $BA8C` | FAC = mem(A/Y) - FAC |
|
|
1415
|
+
| `FMULT` | `$BA28` | `JSR $BA8C` | FAC = mem(A/Y) * FAC |
|
|
1416
|
+
| `FDIV` | `$BB0F` | `JSR $BA8C` | FAC = mem(A/Y) / FAC (the divisor is the one in FAC) |
|
|
1417
|
+
| `FSQR` | `$BF71` | `JSR $BC0C` | FAC = FAC ^ 0.5: FAC to ARG, `MOVFM` of `80 00 00 00 00` at `$BF11`, then the power routine at `$BF7B` |
|
|
1418
|
+
| `FOUT` | `$BDDD` | `LDY #$01` | PETSCII string of FAC at `$0100`, zero-terminated; returns A = `$00`, Y = `$01`, the pointer |
|
|
1419
|
+
| `LINPRT` | `$BDCD` | `STA $62` | prints the unsigned 16-bit integer A (high), X (low) through `CHROUT` |
|
|
1420
|
+
|
|
1421
|
+
The pattern for a sum is: get the first operand into memory as five
|
|
1422
|
+
bytes (a constant in the listing, or `GIVAYF` then `MOVMF` to a
|
|
1423
|
+
scratch), get the second into FAC (`GIVAYF` or `MOVFM`), call the
|
|
1424
|
+
arithmetic entry with A/Y pointing at the memory operand, and read FAC
|
|
1425
|
+
or `MOVMF` it out. For `x / y` put `y` in FAC and point A/Y at `x`.
|
|
1426
|
+
`FOUT` writes into the bottom of the stack page; the string is
|
|
1427
|
+
overwritten by the next `FOUT` and, since it starts at `$0100`, by any
|
|
1428
|
+
stack that grows deep enough, so copy it or print it at once. `FOUT`
|
|
1429
|
+
also alters FAC.
|
|
1430
|
+
|
|
1431
|
+
A five-byte float in memory is an exponent byte in excess-128 form
|
|
1432
|
+
(`$81` is 2^0, `$00` is the value zero) followed by a 32-bit
|
|
1433
|
+
big-endian mantissa whose top bit is implied set and whose bit 7 in
|
|
1434
|
+
the first byte carries the sign instead (0 positive). So 1 is
|
|
1435
|
+
`81 00 00 00 00`, 2 is `82 00 00 00 00`, 10 is `84 20 00 00 00`, and
|
|
1436
|
+
0.1 is `7D 4C CC CC CD`, which the recipe derives and which BASIC's own
|
|
1437
|
+
`1 / 10` reproduced byte for byte. The recipe's `0.1 + 0.2` prints
|
|
1438
|
+
`.3` and `2^24 + 1` prints `16777217`: the mantissa is 32 bits, so the
|
|
1439
|
+
integer that a 24-bit single-precision format loses is exact here.
|
|
1440
|
+
|
|
1441
|
+
The calls own zero page `$57` to `$70`, BASIC's work area (the KERNAL
|
|
1442
|
+
keeps its own variables from `$90` up, by the memory map, rung 4).
|
|
1443
|
+
Over the recipe's whole sequence the bytes that changed were `$57` to
|
|
1444
|
+
`$5D`, `$61` to `$66` (FAC), `$69` to `$6B` and `$6D` (ARG) and `$6F`;
|
|
1445
|
+
`$5E` to `$60`, `$67`, `$68`, `$6C`, `$6E` and `$70` did not change for
|
|
1446
|
+
those inputs, which is not a promise that they never do. A program that
|
|
1447
|
+
keeps its own variables in that span loses them on the first call; one
|
|
1448
|
+
that returns to BASIC afterwards has clobbered nothing BASIC did not
|
|
1449
|
+
expect.
|
|
1450
|
+
|
|
1451
|
+
### Why it works
|
|
1452
|
+
|
|
1453
|
+
BASIC's expression evaluator is the same code with a parser in front:
|
|
1454
|
+
every operator ends as one of these entries with one operand in FAC and
|
|
1455
|
+
the other unpacked into ARG by `CONUPK`. Calling the entries directly
|
|
1456
|
+
skips the tokeniser, the variable lookup and the type checks, and gets
|
|
1457
|
+
the arithmetic at the ROM's own speed. The ROM is visible to the CPU
|
|
1458
|
+
only while bit 0 of `$01` is set with bit 1 also set (`$37`, or `$33`
|
|
1459
|
+
with the character ROM at `$D000`); with it clear a `JSR` fetches
|
|
1460
|
+
whatever the RAM under `$A000` holds. In the recipe's control run that
|
|
1461
|
+
was a zero byte, so the first call executed `BRK`, and the KERNAL's
|
|
1462
|
+
`BRK` handler ran IOINIT, which writes `$E7` to `$01` and banks the ROM
|
|
1463
|
+
back in, cleared the screen and warm-started BASIC: the program
|
|
1464
|
+
vanished into a `READY.` prompt with its verdict byte untouched. The
|
|
1465
|
+
failure is a silent restart, not a wrong number.
|
|
1466
|
+
|
|
1467
|
+
### Cycle budget
|
|
1468
|
+
|
|
1469
|
+
One call each, the input shown, display off, net of a 21-cycle empty
|
|
1470
|
+
call, PAL and NTSC identical: `FMULT` 1,079 (2 * 2), `FDIV` 2,409
|
|
1471
|
+
(355 / 113), `FSQR` 43,752 (sqrt 2), `FOUT` 7,412 (1.41421356). The
|
|
1472
|
+
Cost line carries the multiply as the one call a frame a game might
|
|
1473
|
+
plausibly make; a root is 2.2 PAL frames and does not belong inside
|
|
1474
|
+
one. With the display on the same `FSQR` read 45,975 and `FOUT` 7,969:
|
|
1475
|
+
the CIA counts the cycles badlines take from the CPU, so time these
|
|
1476
|
+
calls with DEN off or accept the stall in the figure. No worst case was
|
|
1477
|
+
sought; the costs vary with the operands' exponents and mantissas and
|
|
1478
|
+
`FOUT` with the digit count.
|
|
1479
|
+
|
|
1480
|
+
### Variations
|
|
1481
|
+
|
|
1482
|
+
- **Print an integer.** `LINPRT` at `$BDCD`, A = high, X = low, prints
|
|
1483
|
+
the unsigned 16-bit value through `CHROUT` with no leading space, as
|
|
1484
|
+
a line number is printed. The recipe prints its four cycle counts
|
|
1485
|
+
with it. It goes through `FOUT` and so costs at least `FOUT`'s
|
|
1486
|
+
7,412 (arithmetic from the measured `FOUT`; `LINPRT` itself was not
|
|
1487
|
+
timed).
|
|
1488
|
+
- **Random numbers.** `RND` is at `$E097`, in the KERNAL ROM image
|
|
1489
|
+
(the BASIC interpreter's tail lives there), so it needs both ROMs
|
|
1490
|
+
in. Its first instructions are `JSR $BC2B` (sign of FAC) then `BMI`,
|
|
1491
|
+
`BNE` and `JSR $FFF3` (IOBASE), which is the three-way split of the
|
|
1492
|
+
published listings, negative reseeds, positive steps, zero reads the
|
|
1493
|
+
CIA timers (the roles rung 4, the instructions rung 1). Put
|
|
1494
|
+
the argument in FAC and call it; the result is a float in FAC in
|
|
1495
|
+
[0, 1). The seed's location was not measured here.
|
|
1496
|
+
- **Subtract and the missing ones.** `FSUB` at `$B850` is verified
|
|
1497
|
+
above and is `mem - FAC`. Powers, logs and trig were not exercised
|
|
1498
|
+
here; the technique gives no address for them.
|
|
1499
|
+
- **Integer out.** For a result back as an integer, the ROM's
|
|
1500
|
+
float-to-integer conversions were not verified in this session; the
|
|
1501
|
+
measured route is `FOUT` and parsing the digits, or `MOVMF` and
|
|
1502
|
+
reading the mantissa against the exponent.
|
|
1503
|
+
|
|
1504
|
+
### Pitfalls
|
|
1505
|
+
|
|
1506
|
+
- `pitfalls/banking.md`, `ram_under_rom_traps`: a program that stores
|
|
1507
|
+
data at `$A000` to `$BFFF` while the ROM is in writes to the RAM
|
|
1508
|
+
underneath and reads the ROM back; and one that banks the ROM out to
|
|
1509
|
+
use that RAM cannot call these routines until it banks it in again,
|
|
1510
|
+
which the recipe's control shows ends in a `BRK`.
|
|
1511
|
+
|
|
1512
|
+
### Recipes
|
|
1513
|
+
|
|
1514
|
+
- `recipes/kickassembler/basic-float-calls.md`: the four sums on
|
|
1515
|
+
screen, the string compares, the ROM dump at every entry, the
|
|
1516
|
+
zero-page before-and-after, the four timings and the banked-out
|
|
1517
|
+
control.
|
|
1518
|
+
|
|
1519
|
+
### Sources
|
|
1520
|
+
|
|
1521
|
+
- The BASIC ROM image 901226-01 and KERNAL 901227-03 as shipped with
|
|
1522
|
+
VICE 3.10, read with the monitor's `d` command (rung 1). The routine
|
|
1523
|
+
names are the conventional labels from the published ROM
|
|
1524
|
+
disassemblies (rung 4); the addresses and first instructions are not.
|