Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/rules/scratch-vm/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ The mesh v2 extension uses AWS AppSync for real-time collaboration:
| `src/blocks/scratch3_operators.js` | regex support | operator_contains で正規表現マッチングをサポート |
| `src/engine/comment.js` | toXML modernization | Blockly v12 対応: `pinned="${!minimized}"` (cherry-pick from upstream spork@29bdbd1fe) + (0,0) 時の x/y 属性省略 (Smalruby 独自) |
| `src/engine/runtime.js` | toolboxitemid for extension categories | Blockly v12 対応: 拡張機能のカテゴリ XML に `toolboxitemid` 属性を追加。Blockly v12 の ContinuousToolbox は `toolboxitemid` から id を読むため、未指定だと `blockly-XXX` の auto-id が StatusIndicatorLabel.extensionId に伝搬し、`!` 接続モーダルが拡張機能を見つけられず scanning で固まる |
| `src/engine/runtime.js` | extension hat shape (modern Blockly) | Blockly v12 対応: 拡張機能の HAT/EVENT ブロックの blockJSON に `shape_hat` extension を付与。modern Blockly は `block.hat === 'cap'`(= `shape_hat` extension)でのみ帽子型を描画し、`ADD_START_HATS` は既定 false。未付与だと甲子園 `connect_game` や micro:bit/顔認識の `when ...` ブロックが帽子型にならず平らな上端で描画される |

### 関連ファイル

Expand Down
10 changes: 10 additions & 0 deletions packages/scratch-vm/src/engine/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,16 @@ class Runtime extends EventEmitter {
}
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
// === Smalruby: Start of extension hat shape (modern Blockly) ===
// In modern Blockly (scratch-blocks v2) the cap-hat shape is only
// drawn when the block carries the `shape_hat` extension (which sets
// block.hat = 'cap'); a missing previousConnection alone is no longer
// enough (ADD_START_HATS defaults to false). The built-in event blocks
// declare extensions: ['colours_event', 'shape_hat'], so mirror that
// here for extension HAT/EVENT blocks (e.g. koshien connect_game,
// micro:bit/face-sensing "when ..." blocks) so they render as hats.
blockJSON.extensions = (blockJSON.extensions || []).concat('shape_hat');
// === Smalruby: End of extension hat shape (modern Blockly) ===
break;
case BlockType.CONDITIONAL:
case BlockType.LOOP:
Expand Down
54 changes: 54 additions & 0 deletions packages/scratch-vm/test/unit/runtime_extension_hat_shape.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const test = require('tap').test;
const Runtime = require('../../src/engine/runtime');
const BlockType = require('../../src/extension-support/block-type');

const categoryInfo = {
id: 'testcat',
name: 'Test',
color1: '#111111',
color2: '#222222',
color3: '#333333',
blocks: [],
customFieldTypes: {},
menus: [],
menuInfo: {}
};

const convert = (rt, blockInfo) => rt._convertForScratchBlocks(blockInfo, categoryInfo);

// Modern Blockly (scratch-blocks v2) only draws the cap-hat shape for blocks
// carrying the `shape_hat` extension. Verify the extension-block conversion adds
// it for HAT/EVENT blocks (so e.g. koshien connect_game renders as a hat) and
// does not turn COMMAND blocks into hats.
test('extension HAT/EVENT blocks get the shape_hat extension', t => {
const rt = new Runtime();

const hat = convert(rt, {opcode: 'whenX', blockType: BlockType.HAT, text: 'when X', arguments: {}});
t.ok(hat.json.extensions, 'HAT has an extensions array');
t.ok(hat.json.extensions.includes('shape_hat'), 'HAT has shape_hat');
t.equal(hat.json.previousStatement, undefined, 'HAT has no previous connection');
t.equal(hat.json.nextStatement, null, 'HAT has a next connection');

const event = convert(rt, {opcode: 'onX', blockType: BlockType.EVENT, text: 'on X', arguments: {}});
t.ok(event.json.extensions.includes('shape_hat'), 'EVENT has shape_hat');

const cmd = convert(rt, {opcode: 'doX', blockType: BlockType.COMMAND, text: 'do X', arguments: {}});
t.notOk(
cmd.json.extensions && cmd.json.extensions.includes('shape_hat'),
'COMMAND does not get shape_hat'
);
t.equal(cmd.json.previousStatement, null, 'COMMAND keeps its previous connection');

// a HAT with an icon keeps the icon extension AND gains shape_hat
const hatWithIcon = convert(rt, {
opcode: 'whenIcon',
blockType: BlockType.HAT,
text: 'when icon',
arguments: {},
blockIconURI: 'data:image/png;base64,AAAA'
});
t.ok(hatWithIcon.json.extensions.includes('shape_hat'), 'icon HAT has shape_hat');
t.ok(hatWithIcon.json.extensions.includes('scratch_extension'), 'icon HAT keeps scratch_extension');

t.end();
});
Loading