Remove rank stamp from combat bar seats
- Drop the `rankT` text object, its draw/update logic and data spec (`rankTextPx`) so seats only show icon, veil, sweep and seconds. - Update `_ensureSkillIcons` fallback wording to reference frame/veil/sweep instead of rank stamp. - Adjust browser test: stop asserting on rank text, recompute seat clip from the seat box center, and remove the rank-pixel regression check. - Refresh screenshot fixtures (`dev/shots/*`) to match the new seat layout.
|
|
@ -25,13 +25,13 @@
|
||||||
"seatOutOfRangePolicy": "ignore"
|
"seatOutOfRangePolicy": "ignore"
|
||||||
},
|
},
|
||||||
"bar": {
|
"bar": {
|
||||||
"_comment": "How a SEATED skill is DRAWN on the combat bar (js/ui/CombatBar.js): the icon size, the state veils, the recharge sweep and the rank stamp. Behaviour lives in code — js/combat/CombatSystem.js → skillSeats() decides WHICH state a seat is in (ready / recharging / not affordable / inert / empty) and this block says what each one LOOKS like. Sizes: the icon is a FRACTION of the seat (the bar scales its seats down on narrow viewports, so absolute pixels would break at 1024px wide); the two text sizes are px inside a ~44px seat. Colours stay CSS strings — the bar converts them at draw time (js/skills/SkillEffects.js → hexToInt).",
|
"_comment": "How a SEATED skill is DRAWN on the combat bar (js/ui/CombatBar.js): the icon size, the state veils and the recharge sweep. Behaviour lives in code — js/combat/CombatSystem.js → skillSeats() decides WHICH state a seat is in (ready / recharging / not affordable / inert / empty) and this block says what each one LOOKS like. Sizes: the icon is a FRACTION of the seat (the bar scales its seats down on narrow viewports, so absolute pixels would break at 1024px wide); the text size is px inside a ~44px seat. Colours stay CSS strings — the bar converts them at draw time (js/skills/SkillEffects.js → hexToInt).",
|
||||||
"iconScale": 0.76,
|
"iconScale": 0.76,
|
||||||
"rankTextPx": 9,
|
"rankTextPx": 9,
|
||||||
"secondsTextPx": 13,
|
"secondsTextPx": 13,
|
||||||
"assignPulseHz": 1.6,
|
"assignPulseHz": 1.6,
|
||||||
"states": {
|
"states": {
|
||||||
"_comment": "Draw order: the icon, then a veil over it (an unranked skill is buried, a too-expensive one is dimmed blue), then the recharge SWEEP — a dark pie that unwinds clockwise as the skill recharges — with the SECONDS LEFT printed over it, then the rank stamp in the corner. `readyEdge` is the outline a live-and-charged seat wears; `assignEdge` outlines every seat while the tree is holding a skill up waiting to be placed.",
|
"_comment": "Draw order: the icon, then a veil over it (an unranked skill is buried, a too-expensive one is dimmed blue), then the recharge SWEEP — a dark pie that unwinds clockwise as the skill recharges — with the SECONDS LEFT printed over it. `readyEdge` is the outline a live-and-charged seat wears; `assignEdge` outlines every seat while the tree is holding a skill up waiting to be placed.",
|
||||||
"readyEdge": "#7ce8ff",
|
"readyEdge": "#7ce8ff",
|
||||||
"inertVeil": "#050a13",
|
"inertVeil": "#050a13",
|
||||||
"inertVeilAlpha": 0.74,
|
"inertVeilAlpha": 0.74,
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 513 KiB After Width: | Height: | Size: 512 KiB |
|
Before Width: | Height: | Size: 484 KiB After Width: | Height: | Size: 530 KiB |
|
Before Width: | Height: | Size: 565 KiB After Width: | Height: | Size: 590 KiB |
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 522 KiB |
|
|
@ -229,56 +229,48 @@
|
||||||
"state": "ready",
|
"state": "ready",
|
||||||
"icon": true,
|
"icon": true,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "5/5",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -290,8 +282,8 @@
|
||||||
"dashing": false,
|
"dashing": false,
|
||||||
"down": false,
|
"down": false,
|
||||||
"ship": {
|
"ship": {
|
||||||
"x": 3810.5218340666174,
|
"x": 5909.759469383378,
|
||||||
"y": 4088.361289263925
|
"y": -988.8723592216014
|
||||||
},
|
},
|
||||||
"ledgerSeats": [
|
"ledgerSeats": [
|
||||||
"ramming_charge",
|
"ramming_charge",
|
||||||
|
|
@ -337,8 +329,8 @@
|
||||||
"maxPoints": 5,
|
"maxPoints": 5,
|
||||||
"energy": 60,
|
"energy": 60,
|
||||||
"cooldown": 6,
|
"cooldown": 6,
|
||||||
"cooldownLeft": 5.722706666666667,
|
"cooldownLeft": 5.733383333333334,
|
||||||
"cooldownFrac": 0.046215555555555454,
|
"cooldownFrac": 0.04443611111111101,
|
||||||
"affordable": false,
|
"affordable": false,
|
||||||
"ready": false,
|
"ready": false,
|
||||||
"inert": false
|
"inert": false
|
||||||
|
|
@ -489,60 +481,52 @@
|
||||||
"state": "cooldown",
|
"state": "cooldown",
|
||||||
"icon": true,
|
"icon": true,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "5/5",
|
|
||||||
"secs": "6"
|
"secs": "6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"energy": 41.66376000000001,
|
"energy": 41.59970000000001,
|
||||||
"maxEnergy": 100,
|
"maxEnergy": 100,
|
||||||
"hull": 6000,
|
"hull": 6000,
|
||||||
"maxHull": 6000,
|
"maxHull": 6000,
|
||||||
|
|
@ -550,8 +534,8 @@
|
||||||
"dashing": true,
|
"dashing": true,
|
||||||
"down": false,
|
"down": false,
|
||||||
"ship": {
|
"ship": {
|
||||||
"x": 3870.629477981919,
|
"x": 5972.397742948351,
|
||||||
"y": 4033.226599552123
|
"y": -675.1068097485484
|
||||||
},
|
},
|
||||||
"ledgerSeats": [
|
"ledgerSeats": [
|
||||||
"ramming_charge",
|
"ramming_charge",
|
||||||
|
|
@ -749,69 +733,61 @@
|
||||||
"state": "noEnergy",
|
"state": "noEnergy",
|
||||||
"icon": true,
|
"icon": true,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "5/5",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"energy": 59.384,
|
"energy": 59.384,
|
||||||
"maxEnergy": 100,
|
"maxEnergy": 100,
|
||||||
"hull": 129,
|
"hull": 101.896,
|
||||||
"maxHull": 129,
|
"maxHull": 129,
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"dashing": false,
|
"dashing": false,
|
||||||
"down": false,
|
"down": false,
|
||||||
"ship": {
|
"ship": {
|
||||||
"x": 3893.832344512312,
|
"x": 5967.269013350007,
|
||||||
"y": 4012.409104914982
|
"y": -697.4064976194823
|
||||||
},
|
},
|
||||||
"ledgerSeats": [
|
"ledgerSeats": [
|
||||||
"ramming_charge",
|
"ramming_charge",
|
||||||
|
|
@ -1009,69 +985,61 @@
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "ready",
|
"state": "ready",
|
||||||
"icon": true,
|
"icon": true,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "5/5",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"energy": 68.21600000000001,
|
"energy": 68.21600000000001,
|
||||||
"maxEnergy": 100,
|
"maxEnergy": 100,
|
||||||
"hull": 121,
|
"hull": 71.896,
|
||||||
"maxHull": 129,
|
"maxHull": 129,
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"dashing": false,
|
"dashing": false,
|
||||||
"down": false,
|
"down": false,
|
||||||
"ship": {
|
"ship": {
|
||||||
"x": 3893.832344512312,
|
"x": 5967.269013350007,
|
||||||
"y": 4012.409104914982
|
"y": -697.4064976194823
|
||||||
},
|
},
|
||||||
"ledgerSeats": [
|
"ledgerSeats": [
|
||||||
null,
|
null,
|
||||||
|
|
@ -1269,69 +1237,61 @@
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"energy": 71.28800000000001,
|
"energy": 71.28800000000001,
|
||||||
"maxEnergy": 100,
|
"maxEnergy": 100,
|
||||||
"hull": 81,
|
"hull": 61.896,
|
||||||
"maxHull": 129,
|
"maxHull": 129,
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"dashing": false,
|
"dashing": false,
|
||||||
"down": false,
|
"down": false,
|
||||||
"ship": {
|
"ship": {
|
||||||
"x": 3893.832344512312,
|
"x": 5967.269013350007,
|
||||||
"y": 4012.409104914982
|
"y": -697.4064976194823
|
||||||
},
|
},
|
||||||
"ledgerSeats": [
|
"ledgerSeats": [
|
||||||
null,
|
null,
|
||||||
|
|
@ -1529,69 +1489,61 @@
|
||||||
"state": "ready",
|
"state": "ready",
|
||||||
"icon": true,
|
"icon": true,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "5/5",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": "0",
|
"frame": "0",
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"state": "empty",
|
"state": "empty",
|
||||||
"icon": false,
|
"icon": false,
|
||||||
"frame": null,
|
"frame": null,
|
||||||
"rank": "",
|
|
||||||
"secs": ""
|
"secs": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"energy": 73.97600000000001,
|
"energy": 73.97600000000001,
|
||||||
"maxEnergy": 100,
|
"maxEnergy": 100,
|
||||||
"hull": 41,
|
"hull": 61.896,
|
||||||
"maxHull": 129,
|
"maxHull": 129,
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"dashing": false,
|
"dashing": false,
|
||||||
"down": false,
|
"down": false,
|
||||||
"ship": {
|
"ship": {
|
||||||
"x": 3893.832344512312,
|
"x": 5967.269013350007,
|
||||||
"y": 4012.409104914982
|
"y": -697.4064976194823
|
||||||
},
|
},
|
||||||
"ledgerSeats": [
|
"ledgerSeats": [
|
||||||
"ramming_charge",
|
"ramming_charge",
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.8 KiB |
|
|
@ -11,8 +11,8 @@
|
||||||
* is the face and the hands):
|
* is the face and the hands):
|
||||||
* · a `?skill=max&combat` run boots with the class filed, both skills 5/5 and
|
* · a `?skill=max&combat` run boots with the class filed, both skills 5/5 and
|
||||||
* the seat Ramming Charge was GIVEN by data filled (nothing to grind);
|
* the seat Ramming Charge was GIVEN by data filled (nothing to grind);
|
||||||
* · the bound seat draws its ICON off the sheet, wears its RANK stamp and reads
|
* · the bound seat draws its ICON off the sheet and reads ready; an unbound
|
||||||
* ready; an unbound seat keeps its reserved empty look;
|
* seat keeps its reserved empty look;
|
||||||
* · pressing 1 dashes: the orb drops by the skill's cost, the seat goes to its
|
* · pressing 1 dashes: the orb drops by the skill's cost, the seat goes to its
|
||||||
* recharge face with the SECONDS printed on it and the sweep unwinds toward
|
* recharge face with the SECONDS printed on it and the sweep unwinds toward
|
||||||
* full charge (evidence in dev/shots/);
|
* full charge (evidence in dev/shots/);
|
||||||
|
|
@ -81,7 +81,6 @@ const snap = () =>
|
||||||
state: x.state,
|
state: x.state,
|
||||||
icon: !!x.icon && x.icon.visible,
|
icon: !!x.icon && x.icon.visible,
|
||||||
frame: x.icon ? String(x.icon.frame?.name ?? '') : null,
|
frame: x.icon ? String(x.icon.frame?.name ?? '') : null,
|
||||||
rank: x.rankT.visible ? x.rankT.text : '',
|
|
||||||
secs: x.secs.visible ? x.secs.text : '',
|
secs: x.secs.visible ? x.secs.text : '',
|
||||||
})),
|
})),
|
||||||
energy: g.combat.energy,
|
energy: g.combat.energy,
|
||||||
|
|
@ -152,11 +151,12 @@ const seatClip = () =>
|
||||||
const r = document.querySelector('canvas').getBoundingClientRect();
|
const r = document.querySelector('canvas').getBoundingClientRect();
|
||||||
const sx = r.width / g.scale.width;
|
const sx = r.width / g.scale.width;
|
||||||
const sy = r.height / g.scale.height;
|
const sy = r.height / g.scale.height;
|
||||||
const m = art.rankT.getWorldTransformMatrix();
|
const cx = art.lx + bar.seatBox.size / 2;
|
||||||
|
const cy = art.ly + bar.seatBox.size / 2;
|
||||||
const S = (bar.seatBox?.size ?? 44) * 1.2;
|
const S = (bar.seatBox?.size ?? 44) * 1.2;
|
||||||
return {
|
return {
|
||||||
x: Math.max(0, Math.round(r.left + (m.tx - S / 2) * sx)),
|
x: Math.max(0, Math.round(r.left + (cx - S / 2) * sx)),
|
||||||
y: Math.max(0, Math.round(r.top + (m.ty - S / 2) * sy)),
|
y: Math.max(0, Math.round(r.top + (cy - S / 2) * sy)),
|
||||||
width: Math.max(8, Math.round(S * sx)),
|
width: Math.max(8, Math.round(S * sx)),
|
||||||
height: Math.max(8, Math.round(S * sy)),
|
height: Math.max(8, Math.round(S * sy)),
|
||||||
};
|
};
|
||||||
|
|
@ -247,17 +247,14 @@ const ready = await page
|
||||||
const s0 = ready.seats[0];
|
const s0 = ready.seats[0];
|
||||||
check(s0.id === RAM.id && s0.rank === RAM.maxPoints, `seat 1 holds ${RAM.label} ${s0.rank}/${s0.maxPoints} — the DEFAULT binding came from data/skills.json`);
|
check(s0.id === RAM.id && s0.rank === RAM.maxPoints, `seat 1 holds ${RAM.label} ${s0.rank}/${s0.maxPoints} — the DEFAULT binding came from data/skills.json`);
|
||||||
check(ready.art[0].icon === true && ready.art[0].frame === String(RAM.frame), `the seat draws icon frame ${RAM.frame} off the sheet (got "${ready.art[0].frame}")`);
|
check(ready.art[0].icon === true && ready.art[0].frame === String(RAM.frame), `the seat draws icon frame ${RAM.frame} off the sheet (got "${ready.art[0].frame}")`);
|
||||||
check(ready.art[0].rank === `${RAM.maxPoints}/${RAM.maxPoints}`, `the seat wears its rank stamp "${ready.art[0].rank}"`);
|
|
||||||
|
|
||||||
// RENDERED PROOF: the rank stamp and the icon each contribute pixels ON TOP of
|
// RENDERED PROOF: the icon contributes pixels ON TOP of the state layers (an
|
||||||
// each other (an icon added lazily over the state layers buries them while every
|
// icon added lazily over the state layers buries them while every property
|
||||||
// property above still reads correct — that regression is what this catches).
|
// above still reads correct — that regression is what this catches).
|
||||||
await clearScreen();
|
await clearScreen();
|
||||||
const seatBase = await shootSeat([]);
|
const seatBase = await shootSeat([]);
|
||||||
const noRank = await shootSeat(['rankT']);
|
|
||||||
const noIcon = await shootSeat(['icon']);
|
const noIcon = await shootSeat(['icon']);
|
||||||
check(Buffer.compare(seatBase, noRank) !== 0, 'the rank stamp actually reaches the pixels (hiding it changes the seat render, so it is not buried under the icon)');
|
check(Buffer.compare(seatBase, noIcon) !== 0, 'the icon reaches the pixels too (so it is not buried under the state layers)');
|
||||||
check(Buffer.compare(seatBase, noIcon) !== 0, 'the icon reaches the pixels too (so the two differ because of stacking, not an empty seat)');
|
|
||||||
fs.writeFileSync(new URL('./shots/skills-seat.png', import.meta.url), seatBase);
|
fs.writeFileSync(new URL('./shots/skills-seat.png', import.meta.url), seatBase);
|
||||||
await page.screenshot({ path: `${SHOTS}skills-bar-ready.png`, timeout: 60000 });
|
await page.screenshot({ path: `${SHOTS}skills-bar-ready.png`, timeout: 60000 });
|
||||||
check(ready.art[0].state === 'ready', `a live, charged, affordable seat reads ready (got ${ready.art[0].state})`);
|
check(ready.art[0].state === 'ready', `a live, charged, affordable seat reads ready (got ${ready.art[0].state})`);
|
||||||
|
|
|
||||||
|
|
@ -8431,14 +8431,14 @@ export class GameScene extends Phaser.Scene {
|
||||||
/**
|
/**
|
||||||
* The skill icons the bar and the console draw (loaded once, on demand, the way
|
* The skill icons the bar and the console draw (loaded once, on demand, the way
|
||||||
* every other UI sheet in the game is). A missing file is not an error: the seats
|
* every other UI sheet in the game is). A missing file is not an error: the seats
|
||||||
* keep their frame, veil, sweep and rank stamp and simply draw no icon.
|
* keep their frame, veil and sweep and simply draw no icon.
|
||||||
*/
|
*/
|
||||||
_ensureSkillIcons() {
|
_ensureSkillIcons() {
|
||||||
if (this._skillIconsTried) return;
|
if (this._skillIconsTried) return;
|
||||||
const sheet = skillIconSheet();
|
const sheet = skillIconSheet();
|
||||||
// The path is the data's (data/skills.json → icons.texture). No path, or a
|
// The path is the data's (data/skills.json → icons.texture). No path, or a
|
||||||
// sheet that never loads, is not an error — the seats keep their frame, veil,
|
// sheet that never loads, is not an error — the seats keep their frame, veil,
|
||||||
// sweep and rank stamp and simply draw no icon.
|
// sweep and seconds and simply draw no icon.
|
||||||
if (!sheet.texture) {
|
if (!sheet.texture) {
|
||||||
this._skillIconsTried = true;
|
this._skillIconsTried = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -8451,7 +8451,7 @@ export class GameScene extends Phaser.Scene {
|
||||||
// issued while another load runs (the deferred comms/media pass, the scene
|
// issued while another load runs (the deferred comms/media pass, the scene
|
||||||
// restart's own queue) is dropped — so this waits for the idle beat instead of
|
// restart's own queue) is dropped — so this waits for the idle beat instead of
|
||||||
// losing the sheet. A handful of beats, then it gives up and the bar draws
|
// losing the sheet. A handful of beats, then it gives up and the bar draws
|
||||||
// seats without icons (frame, veil, sweep and rank stamp still tell the story).
|
// seats without icons (frame, veil and sweep still tell the story).
|
||||||
if (this.load.isLoading?.() === true) {
|
if (this.load.isLoading?.() === true) {
|
||||||
this._skillIconBeats = (this._skillIconBeats ?? 0) + 1;
|
this._skillIconBeats = (this._skillIconBeats ?? 0) + 1;
|
||||||
if (this._skillIconBeats <= 40) this.time.delayedCall(250, () => this._ensureSkillIcons());
|
if (this._skillIconBeats <= 40) this.time.delayedCall(250, () => this._ensureSkillIcons());
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,9 @@ function orbDotKey(scene) {
|
||||||
* the run's bindings live in the skill ledger (js/skills/SkillState.js),
|
* the run's bindings live in the skill ledger (js/skills/SkillState.js),
|
||||||
* a press lands on o.onCast(index) → GameScene.castSkillSeat → the
|
* a press lands on o.onCast(index) → GameScene.castSkillSeat → the
|
||||||
* fight's cast(), and the fight answers every press with a reason. A
|
* fight's cast(), and the fight answers every press with a reason. A
|
||||||
* seat draws its skill's icon (assets/images/skills.png), its rank
|
* seat draws its skill's icon (assets/images/skills.png), a recharge
|
||||||
* stamp, a recharge sweep with the seconds left, a dimmed "cannot
|
* sweep with the seconds left, a dimmed "cannot afford it" veil and
|
||||||
* afford it" veil and an inert veil for a skill nobody has ranked;
|
* an inert veil for a skill nobody has ranked;
|
||||||
* unbound seats keep their reserved look. RIGHT-pressing a seat clears
|
* unbound seats keep their reserved look. RIGHT-pressing a seat clears
|
||||||
* it (making room), and while the skill tree is holding a skill up the
|
* it (making room), and while the skill tree is holding a skill up the
|
||||||
* seats breathe and a press PLACES the skill instead of casting.
|
* seats breathe and a press PLACES the skill instead of casting.
|
||||||
|
|
@ -274,8 +274,8 @@ export class CombatBar extends Phaser.GameObjects.Container {
|
||||||
// layer only draws what each one looks like, and every colour, size and pulse
|
// layer only draws what each one looks like, and every colour, size and pulse
|
||||||
// rate is data-owned (data/skills.json → bar). Draw order per seat: the icon,
|
// rate is data-owned (data/skills.json → bar). Draw order per seat: the icon,
|
||||||
// a VEIL over it (an unranked skill is buried, a too-expensive one dimmed),
|
// a VEIL over it (an unranked skill is buried, a too-expensive one dimmed),
|
||||||
// the recharge SWEEP with the SECONDS left printed over that, and the RANK
|
// the recharge SWEEP with the SECONDS left printed over that. An empty seat
|
||||||
// stamp in the corner. An empty seat draws nothing here — its reserved frame
|
// draws nothing here — its reserved frame
|
||||||
// (above) with its key number IS the empty look.
|
// (above) with its key number IS the empty look.
|
||||||
this.seatArt = [];
|
this.seatArt = [];
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
|
|
@ -300,26 +300,12 @@ export class CombatBar extends Phaser.GameObjects.Container {
|
||||||
.setScrollFactor(0)
|
.setScrollFactor(0)
|
||||||
.setDepth(DEPTH + 1)
|
.setDepth(DEPTH + 1)
|
||||||
.setVisible(false);
|
.setVisible(false);
|
||||||
const rankT = scene.add
|
|
||||||
.text(lx + S - 3, ly + S - 3, '', {
|
|
||||||
fontFamily: fontStack('body', FONT_FALLBACK),
|
|
||||||
fontSize: `${this._barSpec.rankTextPx}px`,
|
|
||||||
fontStyle: 'bold',
|
|
||||||
color: this._barSpec.states.rankColor,
|
|
||||||
stroke: '#02070d',
|
|
||||||
strokeThickness: 2.5,
|
|
||||||
})
|
|
||||||
.setOrigin(1, 1)
|
|
||||||
.setScrollFactor(0)
|
|
||||||
.setDepth(DEPTH + 1)
|
|
||||||
.setVisible(false);
|
|
||||||
this.add(veil);
|
this.add(veil);
|
||||||
this.add(sweep);
|
this.add(sweep);
|
||||||
this.add(edge);
|
this.add(edge);
|
||||||
this.add(secs);
|
this.add(secs);
|
||||||
this.add(rankT);
|
|
||||||
this.seatArt.push({
|
this.seatArt.push({
|
||||||
lx, ly, veil, sweep, edge, secs, rankT,
|
lx, ly, veil, sweep, edge, secs,
|
||||||
state: 'empty', // what the seat is drawing (see _drawSeat)
|
state: 'empty', // what the seat is drawing (see _drawSeat)
|
||||||
icon: null, // created on the first seat that carries a skill
|
icon: null, // created on the first seat that carries a skill
|
||||||
sig: '', // last draw signature — unchanged seats are skipped
|
sig: '', // last draw signature — unchanged seats are skipped
|
||||||
|
|
@ -622,9 +608,8 @@ export class CombatBar extends Phaser.GameObjects.Container {
|
||||||
edge.strokeRoundedRect(...boxIn, rad);
|
edge.strokeRoundedRect(...boxIn, rad);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5 — the printed seconds + the rank stamp.
|
// 5 — the printed seconds.
|
||||||
art.secs.setVisible(cooling).setText(secs);
|
art.secs.setVisible(cooling).setText(secs);
|
||||||
art.rankT.setVisible(bound).setText(`${row.rank}/${row.maxPoints}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The seat's icon image, made on first use (the sheet is loaded once). */
|
/** The seat's icon image, made on first use (the sheet is loaded once). */
|
||||||
|
|
@ -646,10 +631,10 @@ export class CombatBar extends Phaser.GameObjects.Container {
|
||||||
this.add(art.icon);
|
this.add(art.icon);
|
||||||
// The icon is made LAZILY (mid-frame, on the first seat that needs it), so it
|
// The icon is made LAZILY (mid-frame, on the first seat that needs it), so it
|
||||||
// lands on top of the seat's state layers and an opaque sheet would bury the
|
// lands on top of the seat's state layers and an opaque sheet would bury the
|
||||||
// veil, the sweep, the seconds and the rank stamp. Put those back above it —
|
// veil, the sweep and the seconds. Put those back above it —
|
||||||
// veil → sweep → edge → seconds → rank, the order the data describes
|
// veil → sweep → edge → seconds, the order the data describes
|
||||||
// (data/skills.json → bar._comment). The icon never moves again afterwards.
|
// (data/skills.json → bar._comment). The icon never moves again afterwards.
|
||||||
for (const go of [art.veil, art.sweep, art.edge, art.secs, art.rankT]) {
|
for (const go of [art.veil, art.sweep, art.edge, art.secs]) {
|
||||||
if (!go) continue;
|
if (!go) continue;
|
||||||
if (typeof this.bringToTop === 'function') this.bringToTop(go);
|
if (typeof this.bringToTop === 'function') this.bringToTop(go);
|
||||||
else {
|
else {
|
||||||
|
|
@ -801,7 +786,7 @@ export class CombatBar extends Phaser.GameObjects.Container {
|
||||||
this.onSeatUnbind = null;
|
this.onSeatUnbind = null;
|
||||||
for (const art of this.seatArt ?? []) {
|
for (const art of this.seatArt ?? []) {
|
||||||
art.icon = null;
|
art.icon = null;
|
||||||
art.veil = art.sweep = art.edge = art.secs = art.rankT = null;
|
art.veil = art.sweep = art.edge = art.secs = null;
|
||||||
}
|
}
|
||||||
this.seatArt = null;
|
this.seatArt = null;
|
||||||
this.xpLabel = null;
|
this.xpLabel = null;
|
||||||
|
|
|
||||||