Having two different "output modes" was the correct answer, for both 64tass and ACME. FWIW, 64tass is one of the few assemblers that can handle Metroid (128KB) as a single source file: https://6502disassembly.com/nes-metroid/ You're welcome to close this.
Your note made me realize I never followed up on this: the ",k" approach worked fine. You can see it in the output generated for one of the regression tests: https://github.com/fadden/6502bench/blob/master/SourceGen/SGTestData/Expected/20052-branches-and-banks_64tass.S So you're welcome to close this as "working as intended".
Can't use NOT as a label
I'll give ",k" a try. Appending "+$xx0000" felt like a kluge, but I didn't realize there was something more appropriate. cc65 emits JML/JSL if it sees a 24-bit hex operand on JMP/JSR, regardless of destination, so I'm used to fine-tuning operands. Automated regression tests FTW. (FWIW, "JMP" is technically the correct opcode mnemonic; "JML" is accepted as an alias. JSR/JSL are distinct for the reasons you mentioned.) There's a meta-discussion to be had about the role of the assembler and where you...
Ah. So the assembler is effectively emulating a system loader that populates memory with the assembled output, and then grabs a memory dump of the affected range. Seems like I need two output modes, "loadable" and "streaming". "Loadable" is something that could be loaded into a 64K RAM area, so to qualify (start_address + total_length) must be <= 65536 to avoid wrap-around. "Loadable" generated sources begin with "* = addr" and handle logical address changes in the usual way. If start+len > 65536,...
Unexpected need for --long-address
Also fails with v1.55.2176.
Change in 65816 JSR behavior