vm update

This commit is contained in:
minjaesong
2026-04-10 20:36:55 +09:00
parent 102801d8b0
commit ca977b074d
6 changed files with 90 additions and 8 deletions

View File

@@ -12,6 +12,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- TerranBASIC integration
- Multiple platform build system
## Documentations
Documentation for TSVM and TVDOS are available on `./doc/*.tex` as machine-readable format.
Documentatino for TSVM architecture is available on `terranmon.txt`
## Architecture
### Core Components

View File

@@ -368,6 +368,8 @@ let header = {
width: 0,
height: 0,
fps: 0,
fps_num: 0, // Fractional FPS numerator (from XFPS or derived from fps)
fps_den: 1, // Fractional FPS denominator (from XFPS, default 1)
totalFrames: 0,
waveletFilter: 0, // TAV-specific: wavelet filter type
decompLevels: 0, // TAV-specific: decomposition levels
@@ -382,6 +384,22 @@ let header = {
fileRole: 0
}
// Helper function to parse XFPS string ("num/den" format) and update header
function parseXFPS(xfpsStr) {
let parts = xfpsStr.split("/")
if (parts.length === 2) {
let num = parseInt(parts[0], 10)
let den = parseInt(parts[1], 10)
if (!isNaN(num) && !isNaN(den) && den > 0) {
header.fps_num = num
header.fps_den = den
header.fps = num / den
return true
}
}
return false
}
// Read and validate header
for (let i = 0; i < 8; i++) {
header.magic[i] = seqread.readOneByte()
@@ -409,6 +427,9 @@ header.version = seqread.readOneByte()
header.width = seqread.readShort()
header.height = seqread.readShort()
header.fps = seqread.readOneByte()
// Set default fractional fps (will be overridden by XFPS if present)
header.fps_num = header.fps
header.fps_den = 1
header.totalFrames = seqread.readInt()
header.waveletFilter = seqread.readOneByte()
header.decompLevels = seqread.readOneByte()
@@ -461,7 +482,7 @@ const isLossless = (header.videoFlags & 0x04) !== 0
console.log(`TAV Decoder`)
console.log(`Resolution: ${header.width}x${header.height}`)
console.log(`FPS: ${header.fps}`)
console.log(`FPS: ${header.fps === 255 ? "(see XFPS)" : header.fps}`)
console.log(`Total frames: ${header.totalFrames}`)
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
console.log(`Decomposition levels: ${header.decompLevels}`)
@@ -492,20 +513,32 @@ if (isTapFile) {
// Skip non-video packets until we find the image data
while (packetType !== TAV_PACKET_IFRAME) {
if (packetType === TAV_PACKET_EXTENDED_HDR) {
// Skip extended header - parse key-value pairs properly
// Parse extended header - look for XFPS
let numPairs = seqread.readShort()
for (let i = 0; i < numPairs; i++) {
// Skip key (4 bytes)
// Read key (4 bytes)
let keyBytes = seqread.readBytes(4)
let key = ""
for (let j = 0; j < 4; j++) {
key += String.fromCharCode(sys.peek(keyBytes + j))
}
sys.free(keyBytes)
// Read value type and skip value
// Read value type and value
let valueType = seqread.readOneByte()
if (valueType === 0x04) { // Uint64 - 8 bytes
seqread.skip(8)
} else if (valueType === 0x10) { // Bytes - length-prefixed
let length = seqread.readShort()
let dataBytes = seqread.readBytes(length)
// Check for XFPS key
if (key === "XFPS") {
let xfpsStr = ""
for (let j = 0; j < length; j++) {
xfpsStr += String.fromCharCode(sys.peek(dataBytes + j))
}
parseXFPS(xfpsStr)
}
sys.free(dataBytes)
}
}
@@ -1291,7 +1324,7 @@ try {
console.log(`\nStarting file ${currentFileIndex}:`)
console.log(`Resolution: ${header.width}x${header.height}`)
console.log(`FPS: ${header.fps}`)
console.log(`FPS: ${header.fps === 255 ? "(see XFPS)" : header.fps}`)
console.log(`Total frames: ${header.totalFrames}`)
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
console.log(`Quality: Y=${header.qualityY}, Co=${header.qualityCo}, Cg=${header.qualityCg}`)
@@ -1825,7 +1858,19 @@ try {
}
sys.free(dataBytes)
if (interactive) {
// Parse XFPS if present (always try, not just when fps=255)
if (key === "XFPS") {
if (parseXFPS(dataStr)) {
// Update frame timing with new fps
frametime = 1000000000.0 / header.fps
FRAME_TIME = 1.0 / header.fps
if (interactive) {
serial.println(` ${key}: ${dataStr} -> ${header.fps.toFixed(3)} fps`)
}
} else if (interactive) {
serial.println(` ${key}: "${dataStr}" (parse failed)`)
}
} else if (interactive) {
serial.println(` ${key}: "${dataStr}"`)
}
} else {

View File

@@ -303,8 +303,10 @@ MMIO
0: 560x448, 256 Colours, 1 layer
1: 280x224, 256 Colours, 4 layers
2: 280x224, 4096 Colours, 2 layers
3: 560x448, 256 Colours, 2 layers (if bank 2 is not installed, will fall back to mode 0)
4: 560x448, 4096 Colours, 1 layer (if bank 2 is not installed, will fall back to mode 0)
3: 560x448, 256 Colours, 2 layers (if bank 2 is not installed, mode change will not happen)
4: 560x448, 4096 Colours, 1 layer (if bank 2 is not installed, mode change will not happen)
5: 560x448, 15-bit colour, 1 layer (if bank 2 is not installed, mode change will not happen)
8: 560x448, 24-bit colour, 1 layer (if bank 3 and 4 are not installed, mode change will not happen)
4096 is also known as "direct colour mode" (4096 colours * 16 transparency -> 65536 colours)
Two layers are grouped to make a frame, "low layer" contains RG colours and "high layer" has BA colours,
Red and Blue occupies MSBs

View File

@@ -149,6 +149,16 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}
}
fun plotPixelMode1(x: Int, y: Int, colour: Int, plane: Int) {
getFirstGPU()?.let {
val planesize = it.config.width * it.config.height / 4
if (x in 0 until it.config.width/2 && y in 0 until it.config.height/2) {
it.poke(y.toLong() * it.config.width/2 + x + planesize * plane, colour.toByte())
it.applyDelay()
}
}
}
/**
* Sets absolute position of scrolling
*/

View File

@@ -624,6 +624,8 @@ class VM(
return null
}
private val zeroBlock = ByteArray(MALLOC_UNIT)
internal fun malloc(size: Int): Int {
if (size <= 0) throw IllegalArgumentException("Invalid malloc size: $size")
@@ -635,6 +637,22 @@ class VM(
return blockStart * MALLOC_UNIT
}
internal fun calloc(size: Int): Int {
if (size <= 0) throw IllegalArgumentException("Invalid malloc size: $size")
val allocBlocks = ceil(size.toDouble() / MALLOC_UNIT).toInt()
val blockStart = findEmptySpace(allocBlocks) ?: throw OutOfMemoryError("No space for $allocBlocks blocks ($size bytes requested)")
allocatedBlockCount += allocBlocks
mallocSizes[blockStart] = allocBlocks
for (i in 0 until allocBlocks) {
UnsafeHelper.memcpyRaw(zeroBlock, UnsafeHelper.getArrayOffset(zeroBlock), null, usermem.ptr + (blockStart + i) * MALLOC_UNIT, MALLOC_UNIT.toLong())
}
return blockStart * MALLOC_UNIT
}
internal fun free(ptr: Int) {
val index = ptr / MALLOC_UNIT
val count = mallocSizes[index] ?: throw OutOfMemoryError("No allocation for pointer 0x${ptr.toHex()}")

View File

@@ -109,6 +109,7 @@ class VMJSR223Delegate(private val vm: VM) {
fun nanoTime() = System.nanoTime()
fun malloc(size: Int) = vm.malloc(size)
fun calloc(size: Int) = vm.calloc(size)
fun memset(dest: Int, ch: Int, count: Int) = vm.memset(dest, ch, count)
fun free(ptr: Int) = vm.free(ptr)
fun forceAlloc(ptr: Int, size: Int) = vm.forceAlloc(ptr, size)